Yet another discussion on having native(some what) MQTT support with Phoenix

I am new to Phoenix (Erlang and Elixir ecosystem in general), I am from an IoT company which does all its stuff in MQTT [current stack is - EMQTT + Paho + Django + Celery + PostgreSQL], basically a broker where all devices are conencted - the backend subscribes to “root topic - #” to get all messages - the messages are handled by celery workers in background. These setup is not so cohorent as too many moving components. Phoenix looks a good setup to do such kind of things - only issue is that the devices uses MQTT (and we dont want to move to any other lib as this one is stable enough).

I went through various threads [thread 1 and thread 2] discussing native “transport layer” support for MQTT with Phoenix. Looks like there is no plan to add it to Phoenix and would require some overhaul (I might have misunderstood).

I have another idea of getting this done - with “MQTT over Websocket” support instead of “native MQTT” support. The lib we use on firmware end has this feature - wrapping MQTT packets with WS packets, not sure how it works exactly. But this would eliminate having a MQTT transport layer support and process the MQTT message later with some custom Plugs.

I am a noob when it comes to Phoenix (Just went through “Getting started in docs” and some YT videos), so forgive my ignorance. But I think this could be a relatively easier option to have MQTT support in Phoenix.

Hello and welcome,

You might be interested in Brodway, and MQTT connector…

You might build a similar pipeline with…

MQTT connector + Broadway + Phoenix + Task + PostgreSQL

Hi there :slight_smile:

Funny to see that discussion referenced. Yeah, I wanted to add MQTT support to Phoenix, but I eventually found that building a 100% compatible implementation would either be impossible or just a huge amount of work. Mainly because MQTT actually has quite a lot of features - not just the topic subscription logic, but also things like persistent sessions. And now we have MQTT v5, which can do so many more things.

But, if you have a simple scenario, where you control all the MQTT clients, and you can impose your own limits on which MQTT features you use, it is definitely possible to implement an MQTT transport for Phoenix channels. You should think about how to map Phoenix Channels to MQTT in the simplest way possible.

I ended up writing a simple Telnet transport adapter for Phoenix Channels: GitHub - trarbr/telly: a proof of concept telnet transport for the Phoenix framework and also blogged about it (part one and part two). That was a long time ago though, and I think the Phoenix Transport API has changed slightly.

Thanks @trarbr for the reply and your work on Telly.

I am aware that native MQTT transport will be a huge task, that is why I am suggesting support for “MQTT over Websocket” - which is de-facto industry standard when using MQTT in an environment (eg - browsers) that do not support MQTT. In the IoT world that is how it is done - broker supports this feature - where is communicates with browser via WS with MQTT message inside it - at client side the messages are wrapped/unwrapped.

There for can we write an MQTT Plug that is added at “Endpoint” level (:mqtt pipeline so to speak) - where it unwraps the message and does translation of MQTT to Phoenix components (channels, etc). Now I agree there could be certain features which could not be implemented in this setup - but I am fine with basic Publish/Subscribe and Authentication/Authorization features.

Let me know what you think of this idea. I might be just be dreaming (or wishful thinking) stuff over here!

Implementing MQTT over WebSockets is definitely an option, but I do not think it will make a huge difference vs. implementing MQTT over TCP. One thing WS could have going for it is the framing format - that maybe a WS frame would match an MQTT packet, so you didn’t have to worry about handling incomplete packets, but it seems not to be the case. To quote from that link:

A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries

And it’s not really the transport layer that has the most complexity - it’s in the “what do I do when I have received an MQTT message”.

Anyway, let’s try and make a list of what you’ll have to do:

  • Implement a streaming MQTT packet decoder. You can find code for this on GitHub, take a look at Tortoise fo example.
  • Match an MQTT connection to a Phoenix.Socket. This is one thing I expect WS would make easier than TCP, since the WS will indicate a path when it connects, which is also how “Phoenix native” WS maps connections to Phoenix.Socket. If using TCP, you could listen on different ports if you want to use more than one socket. Like, port 1883 goes to MyApp.UserSocket and port 1884 goes to MyApp.AdminUserSocket.
  • Handle the MQTT Connect packet, by extracting username and password field as params to the Socket’s connect/2 function
  • Handle MQTT publishes. This is quite different than with “Phoenix native” channels, because in “Phoenix native” channels, clients always join a channel before publishing (and the channel join is where authorization happens). This is not the case in MQTT, where clients can publish to any topic they want at any point after connecting, and authorization is handled individually for each packet that’s published.
  • Handle MQTT subscribes. This is more like “Phoenix native” channels, as your MQTT client indicates a topic (or a list of topics) it wants to subscribe to, which can be matched to Phoenix channels. However, Phoenix channels do not support wildcard subscriptions. If you don’t use the feature, that might be OK. But wildcard subscriptions are (IMHO) a killer feature of MQTT, and I’m afraid if you choose not to implement it you might regret it later.

Those are the things I think of right off the top of my head.

Of course, this is not to say that it cannot be done :slight_smile: Just to illustrate some of the differences. You will probably want to make trade offs which may work for your case, but might not work for MQTT in general.

why not just use a mqtt client (eg GitHub - emqx/emqtt: Erlang MQTT v5.0 Client) and build some kind of gateway? As @trarbr already pointed out mqtt is a lot more than transport (in emqtt transport is just about 1% of the code)

Looks like MQTT client based approach is the most implementable at this point of time (which I am already doing it in Python stack). I was considering re-write of the current backend of Phoenix/Elixir as is more suitable for such real-time applications.

Anyways as I understood there I have two options:

  1. stick to current backend architecture (with python or re-write in Phoenix/Elixir with same archetectue - external Broker)
  2. move firmware/device side to natively use Websocket (and backend to Phoenix)- in this case the connection will be more Point-To-Point with backend, with “Phoenix Topic and Channel” giving a pub/sub kind of feature (something like what MQTT does), Not sure if this would be more performant (in terms of throughput,latency, connection stability,etc) than option 1 (could be that MQTT broker outperforms PhoenixPubSub). Also, in this way I am not able to wrap my mind around - how to co-ordinate things between devices in Phoenix Channels term, also how to isolate tenants (devices are “owned” by tentant - and those tentants can only listen and publish to their devices) - not sure how to do it with Phoenix topics - in MQTT we use <tenant-id>/<device-id>/<action> format where device subscribe to <tenant-id>/<device-id>/# and the tentant subscribes to <tentant-id>/#. And in the end I also how to ask whether this transition would give enough gains. The question really is do I want my business logic to seat in front/on-top (with Phoenix) or at the back (with Broker+client) of pubsub system (correct me if I got it wrong) and I am not sure which system is better for my use-case.

Anyways, any ideas on using Erlang/Elixir based MQTT broker (VerneMQ) as it with some plugins that integrates Broker with Phoenix Channels/Topic (Not sure if it even makes sense)?

Well if you want to, you could write a VerneMQ plugin that includes Phoenix. So basically host your entire web application inside the broker. (Maybe this can be done with EMQTT as well - I don’t know its plugin system though). Then implement the VerneMQ plugin hooks you need, so that it triggers whatever you want to happen on the web.

It sort of depends on what you need, though. If what you want is to run a simple background job on every message, you could write that logic to be executed directly in the VerneMQ plugin hooks. Or you could include Oban, and create Oban jobs from the VerneMQ plugin hooks. You would not need Phoenix for any of that - only if you want to provide a web frontend.

There’s a slightly old Wiki page on writing Elixir plugins for VerneMQ here: VerneMQ plugins in Elixir · vernemq/vernemq Wiki · GitHub . As the plugin you write is a complete OTP application, it can include other OTP applications (like Phoenix or Oban).