Structuring a Nerves/Phoenix application

Hi, I’m relatively new to the Elixir/OTP world and I’m wondering how to structure a Phoenix/Nerves application

I’m building a project to control IKEA lights on a Raspberry Pi, and I currently have a phoenix application alongside a nerves one in a ‘Poncho’ project.

I’m currently using Phoenix Pub/Sub to communicate between the two, which works well. However, I want to be able to keep track of the state of the lights which seems difficult. It makes sense to me to have the light state in the firmware part of the application where the lights are controlled, but I’m not sure how to access this state on demand from Phoenix.

I can’t depend on the Nerves application from the web one as this would create a circular dependency, and I don’t want to roll my own request/response system over PubSub.

What would be the best way to create a single source of truth for my application that both parts can depend on?

For those interested, the project is here: https://github.com/th0mas/Fancy-Lights.
Relevant files are in fancy_lights_firmware/lib/fancy_lights_firmware/light_manager.ex and fancy_lights_ui/lib/fancy_lights_ui_web/channels/light_channel.ex

Many Thanks.

1 Like

Have you thought of using Phoenix presence?

I’ve run into the same sort of issue when learning how to use LED lights on my Raspberry Pi. I think the thing to remember is that both projects are running in the same BEAM instance, so they can communicate via normal process message passing. In my case, I gave each LED a Genserver in the nerves app, and hardcoded a name for it like so:

  def start_link(output_pin) do
    GenServer.start_link(
      __MODULE__,
      %{pin: output_pin, circuit_ref: nil, active: false},
      name: :red_light
    )
  end

So then from my Phoenix app I could just do the following: GenServer.call(:red_light, :turn_on) and it would work.

Not sure if that’s the “right” way to do it, but it doesn’t require using Phoenix PubSub from the Nerves end.

1 Like

Funny enough, I was just having this exact same questionin a Nerves/Phoenix application. It makes sense to keep device state-sorts of things in the firmware side, but as soon as you start wanting to manipulate things from the Phoenix application, it gets messy!

There’s a couple approaches I’ve been thinking of taking. The easiest approach would be to just place all the business and UI logic both in the Phoenix app (in which case the “firmware” application would just be for packaging/bootstrapping the Phoenix application). As you say though, this doesn’t necessarily “feel” right.

Another approach would be to define the GenServers and their interfaces in the Phoenix layer, but actually have them supervised and run in the firmware layer. Then you could use your existing PubSub (or just the Registry) to inform the UI layer of their PIDs, but still have all the GenServer client code accessible from the UI layer.

Or, you could extract any code that both the UI and firmware need to be able to call out into a 3rd dependency, more of a “library” dependency than an application. Again, the processes could be started in and supervised by the firmware, but once the firmware told the UI what PIDs to use, the UI could call IkeaLights.change_colour/1.

(Of course in your sample application it looks like IkeaLights is a named GenServer, so I guess you wouldn’t even need to communicate the PID from the firmware to the UI application.)

I’m sure there are other good approaches, these are the first few that come to mind for me though.

1 Like

I was thinking about doing this and calling the GenServer straight from the UI application, but I was under the impression this wouldn’t be the most “idiomatic” approach. The ‘recommended’ way to interact with GenServers seems to be using wrappers to you don’t have to call GenServer.call/2 directly - although I could be wrong and an happy to be corrected on this.

On making a 3d dependency, this does seem like a good option that would make ‘logical’ sense, but it seems like a lot of overhead and boilerplate for what is essentially a toy project.

I’ve just done a bit of googling on this but there doesn’t seem to be an easy way to register a global ‘Presence’ that tracks my application state and can be accessed from outside the Phoenix app - am I missing something here?

Again, I’m new to the elixir way of doing things having come from a mostly imperative/OO background so all input is appreciated :slight_smile:

I’d say @jordan0day’s approach is the most idiomatic-simple. If you want something slightly more sophisticated you can set your nerves module to an “@ tribute” inside the Phoenix module, which lets you mock values in test (use Mox!!).

The presence idea I think is also idiomatic. Obviously more complex, but the chief difference being “it’s push” versus the simple module being “pull”, requiring a polling loop in your Phoenix module if you want real-time updates.

I’m not sure what you mean by global Presences? But I feel like Presence will take care of that for you*! Just remember to configure presence application only on one app (probably the one that has the pubsub) and only use it as a dependency on the other apps.

*Remember that presence tracks processes which by their nature are global. I have a system in prod where I track Presences and then forward the pubsub messages to an aggregator 1000s of miles away using TLS. Works just fine.

1 Like

Thanks for the input. For future reference, I’ve gone for the simple solution of calling a named GenServer from my UI application - I’ll leave figuring out Phoenix Presence for another day :slight_smile:

Thanks for all the help

2 Likes