"Connection refused" when accessing the target on nerves.local

Hello everyone!

I am completely new to Nerves and this is my first “project”. I am trying to build a small server that streams the video from the picam to a simple web-page via wifi. Basically I try to test/implement the picam-http example project, using some additional network configurations from nerves-network .

I have tried two things to connect my Raspberry Pi and my laptop via wifi:

  1. Turned my Raspberry Pi to an access point, creating its own network and connecting my laptop to this network.

  2. Connected my Raspberry Pi to my home network with my SSID and PSK.

In the first case the created network showed up after the boot of the Raspberry Pi and I could connect to it with my laptop. In both cases I could ping the target-device after the boot with ping nerves.local for my home network and ping ip-address for the access-point network and I recevied the reply. After the boot the LED on my picam was also on. So everything seemed to work at that point.

But when I tried to access the http://nerves.local:4001/video (http://ip-address:4001/video as well), where the video from picam was supposed to be streamed, I always got “connection refused” message. (Tried http://nerves.local:4001/ and http://nerves.local/ too but got the same error). The ip-address and the domain name must be right.

Could you please help me and tell, what my mistakes are, what I am doing wrong or what I am missing?

My system is:

  • Host: Windows 10 using Virtual Box with Ubuntu 18.04.3

  • Target: Raspberry Pi 3 B+

  • Picam RB-Camera-WW2

  • SD-Card, USB-Cable

Configuration file config.exs

# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

config :test_cam, target: Mix.target()

# Customize non-Elixir parts of the firmware. See
# https://hexdocs.pm/nerves/advanced-configuration.html for details.

config :nerves, :firmware, rootfs_overlay: "rootfs_overlay"

# Use shoehorn to start the main application. See the shoehorn
# docs for separating out critical OTP applications such as those
# involved with firmware updates.

config :shoehorn,
  init: [:nerves_runtime, :nerves_init_gadget],
  app: Mix.Project.config()[:app]

# Use Ringlogger as the logger backend and remove :console.
# See https://hexdocs.pm/ring_logger/readme.html for more information on
# configuring ring_logger.

config :logger, backends: [RingLogger]

if Mix.target() != :host do
  import_config "target.exs"
end

Target configuration file target.exs

use Mix.Config

# Authorize the device to receive firmware using your public key.
# See https://hexdocs.pm/nerves_firmware_ssh/readme.html for more information
# on configuring nerves_firmware_ssh.

keys =
  [
    Path.join([System.user_home!(), ".ssh", "id_rsa.pub"]),
    Path.join([System.user_home!(), ".ssh", "id_ecdsa.pub"]),
    Path.join([System.user_home!(), ".ssh", "id_ed25519.pub"])
  ]
  |> Enum.filter(&File.exists?/1)

if keys == [],
  do:
    Mix.raise("""
    No SSH public keys found in ~/.ssh. An ssh authorized key is needed to
    log into the Nerves device and update firmware on it using ssh.
    See your project's config.exs for this error message.
    """)

config :nerves_firmware_ssh,
  authorized_keys: Enum.map(keys, &File.read!/1)

# Configure nerves_init_gadget.
# See https://hexdocs.pm/nerves_init_gadget/readme.html for more information.

# Setting the node_name will enable Erlang Distribution.
# Only enable this for prod if you understand the risks.
node_name = if Mix.env() != :prod, do: "test_cam"

config :test_cam, interface: :wlan0, port: 4001

config :nerves_init_gadget,
  ifname: "wlan0",
  address_method: :dhcp,
  mdns_domain: "nerves.local",
  node_name: node_name
  #node_host: :mdns_domain

#### networkconfig
key_mgmt = System.get_env("NERVES_NETWORK_KEY_MGMT") || "WPA-PSK"

config :nerves_network, :default,
  wlan0: [
    ssid: System.get_env("NERVES_NETWORK_SSID"),
    psk: System.get_env("NERVES_NETWORK_PSK"),
    key_mgmt: String.to_atom(key_mgmt)
  ],
  eth0: [
    ipv4_address_method: :dhcp
  ]
# Import target specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
# Uncomment to use target specific configurations

# import_config "#{Mix.target()}.exs"

mix.exs

defmodule TestCam.MixProject do
  use Mix.Project

  @app :test_cam
  @version "0.1.0"
  @all_targets [:rpi, :rpi0, :rpi2, :rpi3, :rpi3a, :rpi4, :bbb, :x86_64]

  def project do
    [
      app: @app,
      version: @version,
      elixir: "~> 1.9",
      archives: [nerves_bootstrap: "~> 1.6"],
      start_permanent: Mix.env() == :prod,
      build_embedded: true,
      aliases: [loadconfig: [&bootstrap/1]],
      deps: deps(),
      releases: [{@app, release()}],
      preferred_cli_target: [run: :host, test: :host]
    ]
  end

  # Starting nerves_bootstrap adds the required aliases to Mix.Project.config()
  # Aliases are only added if MIX_TARGET is set.
  def bootstrap(args) do
    Application.start(:nerves_bootstrap)
    Mix.Task.run("loadconfig", args)
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      mod: {TestCam.Application, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # Dependencies for all targets
      {:nerves, "~> 1.5.0", runtime: false},
      {:shoehorn, "~> 0.6"},
      {:ring_logger, "~> 0.6"},
      {:toolshed, "~> 0.2"},
      {:picam, "~> 0.4.0"},
      {:cowboy, "~> 1.0.0"},
      {:plug, "~> 1.0"},

      # Dependencies for all targets except :host
      {:nerves_runtime, "~> 0.6", targets: @all_targets},
      {:nerves_init_gadget, "~> 0.4", targets: @all_targets},
      {:nerves_firmware_ssh, "~> 0.3", targets: @all_targets},

      # Dependencies for specific targets
      {:nerves_system_rpi, "~> 1.8", runtime: false, targets: :rpi},
      {:nerves_system_rpi0, "~> 1.8", runtime: false, targets: :rpi0},
      {:nerves_system_rpi2, "~> 1.8", runtime: false, targets: :rpi2},
      {:nerves_system_rpi3, "~> 1.8", runtime: false, targets: :rpi3},
      {:nerves_system_rpi3a, "~> 1.8", runtime: false, targets: :rpi3a},
      {:nerves_system_rpi4, "~> 1.8", runtime: false, targets: :rpi4},
      {:nerves_system_bbb, "~> 2.3", runtime: false, targets: :bbb},
      {:nerves_system_x86_64, "~> 1.8", runtime: false, targets: :x86_64},
    ]
  end

  def release do
    [
      overwrite: true,
      cookie: "#{@app}_cookie",
      include_erts: &Nerves.Release.erts/0,
      steps: [&Nerves.Release.init/1, :assemble],
      strip_beams: Mix.env() == :prod
    ]
  end
end

application.ex

defmodule TestCam.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    Picam.Camera.start_link
    opts = [strategy: :one_for_one, name: TestCam.Supervisor]
    children =
      [
        # Children for all targets
        # Starts a worker by calling: TestCam.Worker.start_link(arg)
        # {TestCam.Worker, arg},
      ] ++ children(target())

    Supervisor.start_link(children, opts)
  end

  # List all child processes to be supervised
  def children(:host) do
    [
      # Children that only run on the host
      # Starts a worker by calling: TestCam.Worker.start_link(arg)
      # {TestCam.Worker, arg},
    ]
  end

  def children(_target) do
    [
      # Children for all targets except host
      # Starts a worker by calling: TestCam.Worker.start_link(arg)
      # {TestCam.Worker, arg},
      Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: TestCam.Router, options: [port: 4001])
    ]
  end

  def target() do
    Application.get_env(:test_cam, :target)
  end
end

Thanks in advance and please let me know, if you need any additional information about the system or code.

If you could post the TestCam.Router that would be helpful.

Also, you should put Picam in your children list so that it’s supervised as well.

Hi, thank you for your quick reply!

Here is my TestCam.Router (I took the one from the picam-http example and did not change it):

router.ex

defmodule TestCam.Router do

  use Plug.Router

  plug :match
  plug :dispatch

  get "/" do
    markup = """
    <html>
    <head>
      <title>Picam Video Stream</title>
    </head>
    <body>
      <img src="video.mjpg" />
    </body>
    </html>
    """
    conn
    |> put_resp_header("Content-Type", "text/html")
    |> send_resp(200, markup)
  end

  forward "/video.mjpg", to: TestCam.Streamer

  match _ do
    send_resp(conn, 404, "Oops. Try /")
  end

end

Thanks for your advice!

Did you mean something like this?

def start(_type, _args) do
    opts = [strategy: :one_for_one, name: TestCam.Supervisor]
    children =
      [
         Picam.Camera.start_link
      ] ++ children(target())

    Supervisor.start_link(children, opts)
  end

So it looks like the url should be http://nerves-ip:4001/ or http://nerves.local:4001/ or http://nerves.local:4001/video.mjpg or http://nerves-ip:4001/video.mjpg

I don’t see anywhere where /video would resolve.

given the error, it may be that Cowboy is crashing on startup as well, so viewing your logs on the Pi may be helpful.

Oh, that’s a really good point. But I have just tried with “/video.mjpg” too and unfortunatelly still getting “connection refused”.

Okay, is there any log-file in the project folder that I could show or where can I get the logs?

If you have it configured correctly, you should be able to see them over the USB serial connection.

Another quick sanity check would be to comment out the Picam, and try running it locally, then go to http://localhost:4001/

Also, make sure your Picam is actually connected :wink:

If you can ssh to the node, you can also run RingLogger.attach and see the logs

Sorry, I did not understand anything :sweat_smile: Do you mean to run locally the single elixir-file (application.ex)? And locally means on my host, so on my laptop, right? (If so, I will try it and give a feedback tomorrow. It is already late night at my place :sweat_smile:)

Yeah, comment out the Picam.Camera.start_link, and run iex -S mix in your project directory on your laptop, that will at least let you know if everything is starting up ok.

Hi, so I did some pictures of the logs, that I got after booting and after RingLogger.next command (sorry for the bad quality). They seem to be too big to be uploaded, so I created some links for the Dropbox

After booting

RingLogger.next

I tried to start the project locally (with iex -S mix) and I got this error. I tried to google it but did not find any solution and I don’t know what caused the error.

I have one more file with some code that I have not posted yet - the streamer. I don’t know, if it helps, but i post it too.

streamer.ex

defmodule TestCam.Streamer do
  @moduledoc """
  Plug for streaming an image
  """
  import Plug.Conn

  @behaviour Plug
  @boundary "w58EW1cEpjzydSCq"

  def init(opts), do: opts

  def call(conn, _opts) do
    conn
    |> put_resp_header("Age", "0")
    |> put_resp_header("Cache-Control", "no-cache, private")
    |> put_resp_header("Pragma", "no-cache")
    |> put_resp_header("Content-Type", "multipart/x-mixed-replace; boundary=#{@boundary}")
    |> send_chunked(200)
    |> send_pictures
  end

  defp send_pictures(conn) do
    send_picture(conn)
    send_pictures(conn)
  end

  defp send_picture(conn) do
    jpg = Picam.next_frame
    size = byte_size(jpg)
    header = "------#{@boundary}\r\nContent-Type: image/jpeg\r\nContent-length: #{size}\r\n\r\n"
    footer = "\r\n"
    with {:ok, conn} <- chunk(conn, header),
         {:ok, conn} <- chunk(conn, jpg),
         {:ok, conn} <- chunk(conn, footer),
      do: conn
  end
end

When you tried to start the project locally, did you comment out Picam.Camera.start_link?

Based on the error above, I don’t think you did.

The other logs above don’t show any issues, but they aren’t sufficient to debug anything. It would be helpful to see the end of the logs.

Thanks, you are right! I did commented out “Picam.Camera.start_link” in the application file, but I forgot to comment out “jpg = Picam.next_frame” and so on in the streamer. I commented out the whole body of the method “send_picture(conn)” in the streamer and I managed to start the project locally. I had no errors but when I navigated to “http://localhost:4001/”, I got the “unable to connect” error.

That was supposed to be the end of logs but I will double check that.

Hmm, well something is wrong with your Cowboy configuration.

Do you see any additional logs when you hit http://localhost:4001/ ?

If you can push this to a public repository, it would be easier for me to help debug your issue.