Home Posts Tags Post Search Tag Search

Post 66

Weather App 13 - Publishing Sensor Data 02

Published on: 2025-08-29 Tags: elixir, Generators , Blog, Side Project, Phoenix, Nerves, Weather App, Poncho
Publishing Metrics
        Now that we have the framework to add new entry to the db and have the route to do so we can now work on a publisher. Head back to the main sensor_hub_poncho and run.
            mix new publisher

        Get to the file publisher.ex within the new publisher directory. Start by implemeting the start_link and init.
            defmodule Publisher do
                use GenServer

                require Logger

                def start_link(options \\ %{}) do
                    GenServer.start_link(__MODULE__, options, name: __MODULE__)
                end

                @impl true
                def init(options) do
                    state = %{
                    interval: options[:interval] || 10_000,
                    # || "http://localhost:4000/api/weather-conditions"
                    weather_tracker_url: options[:weather_tracker_url],
                    sensors: options[:sensors],
                    measurements: :no_measurements
                    }

                    schedule_next_publish(state.interval)
                    {:ok, state}
                end

                defp schedule_next_publish(interval) do
                    Process.send_after(self(), :publish_data, interval)
                end
            end

        This takes care of most of the needs of the Publisher. But keep in mind that and Process.send_after needs a handle_info and that handle info will need to measure and then publish after.
            @impl true
            def handle_info(:publish_data, state) do
                {:noreply, state |> measure() |> publish()}
            end

            defp measure(state) do
                measurements =
                Enum.reduce(state.sensors, %{}, fn sensor, acc ->
                    sensor_data = sensor.read() |> sensor.convert.()
                    Map.merge(acc, sensor_data)
                end)

                %{state | measurements: measurements}
            end

            defp publish(state) do
                result =
                :post
                |> Finch.build(state.weather_tracker_url,
                [{"Content-Type", "application/json"}],
                Jason.encode!(state.measurements)
                )
                |> Finch.request(WeatherTrackerClient)

                Logger.debug("Server response: #{inspect(result)}")

                schedule_next_publish(state.interval)

                state
            end

        The last line of the pipe for publish Finch.request(WeatherTracerClient) is using an existing module to do the communication.

        This is to make sure that we can connect to the site that will keep everything up to date. Finch is what we will use to take care of the connections. With this you will need to add the needed deps for the jason and finch. Do that right now.

            deps [
                ...
                {:finch, "~> 0.6.3"},
                {:jason, "~> 1.2.2}
            ]

    Hooking It into the Firmware Project
        Now we need to go back to the sensor_hub project to add the new modules into the children and set the entire project. We will need to add the publisher and make sure that it has access to the different sensors.
            ...
            alias SensoHub.Sensor
            ...

            def children(_target) do
                # This function returns the child processes to be supervised
                # Here you can define the children for your application
                [
                    {Bme280, %{}},
                    {SGP40, %{bus_name: "i2c-1", name: SGP40}},
                    {TSL25911FN, %{}},
                    {LTR390_UV, %{}},
                    {Finch, name: WeatherTrackerClient},
                    {
                        Publisher,
                        %{
                        sensors: sensors(),
                        weather_tracker_url: weather_tracker_url()
                        }
                    }
                ]
            end

            defp sensors() do
                [
                Sensor.new(Bme280),
                Sensor.new(SGP40),
                Sensor.new(TSL25911FN),
                Sensor.new(LTR390_UV)
                ]
            end

            defp weather_tracker_url() do
                Application.get_env(:sensor_hub, :weather_tracker_url)
            end

        This is a lot of small things that do a lot. We are making sure that the publisher has access to all the sensors, we are adding in the finch with a name that will need to be refrenced in perious code. Also we are setting up a environmental bit of information that will be the location of the localhost of the pi.

        Lastly we will need to set the enviromental value for the :weather_tracker_url this is done within the sensor_hub/config/target.exs
            config :sensor_hub, :weather_tracker_url,
                "http://<SERVER_IP_ADDRESS>:4000/api/weather-conditions"

        Lastly lastly you need to set the dependencies for the new modules to the sensor_hub/mix.exs
            defp deps do
                [
                    # Dependencies for all targets
                    ...
                    # Dependencies for all targets except :host
                    {:publisher, path: "../publisher", targets: @all_targets},
                    ...
                    # Dependencies for specific targets
                    ...
                ]
            end
        
        With all this in place 
            # Make sure that from weather_tracker run
            docker compose up -d

            # From weather_tracker run
            mix phx.server

            mix firmware
            mix ./upload.sh 192.168.xx.xx

            ssh 192.168.xx.xx

            # Once inside

            RingLogger.attach()