Skip to content

Commit

Permalink
Start working on v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
GregMefford committed Dec 9, 2018
1 parent 9cd6125 commit 535a66c
Show file tree
Hide file tree
Showing 28 changed files with 1,596 additions and 440 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ erl_crash.dump
*.ez
_images
/priv
rel/nerves_system_libs
rel/nerves_io_neopixel
*.o
.DS_Store
.tool-versions
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2016 Greg Mefford
Copyright (c) 2016-2017 Greg Mefford

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
39 changes: 15 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,25 @@
# CC C compiler
# CROSSCOMPILE crosscompiler prefix, if any
# CFLAGS compiler flags for compiling all C files
# ERL_CFLAGS additional compiler flags for files using Erlang header files
# ERL_EI_LIBDIR path to libei.a
# LDFLAGS linker flags for linking all binaries
# ERL_LDFLAGS additional linker flags for projects referencing Erlang libraries

# Look for the EI library and header files
# For crosscompiled builds, ERL_EI_INCLUDE_DIR and ERL_EI_LIBDIR must be
# passed into the Makefile.
ifeq ($(ERL_EI_INCLUDE_DIR),)
ERL_ROOT_DIR = $(shell erl -eval "io:format(\"~s~n\", [code:root_dir()])" -s init stop -noshell)
ifeq ($(ERL_ROOT_DIR),)
$(error Could not find the Erlang installation. Check to see that 'erl' is in your PATH)
endif
ERL_EI_INCLUDE_DIR = "$(ERL_ROOT_DIR)/usr/include"
ERL_EI_LIBDIR = "$(ERL_ROOT_DIR)/usr/lib"
endif
# Set Erlang-specific compile and linker flags
ERL_CFLAGS ?= -I$(ERL_EI_INCLUDE_DIR)
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR) -lei

LDFLAGS +=

CFLAGS += -std=gnu99
# Enable for debug messages
CFLAGS += -DDEBUG

CC ?= $(CROSSCOMPILER)gcc

SRC = $(wildcard src/*.c) src/rpi_ws281x/dma.c src/rpi_ws281x/mailbox.c \
src/rpi_ws281x/mailbox.c src/rpi_ws281x/pwm.c src/rpi_ws281x/rpihw.c \
ifeq ($(NERVES_TOOLCHAIN),)
# Host testing build
CFLAGS += -DDEBUG
SRC = src/rpi_ws281x.c src/fake_ws2811.c
endif
ifneq ($(NERVES_TOOLCHAIN),)
# Normal build
SRC = src/rpi_ws281x.c src/rpi_ws281x/dma.c src/rpi_ws281x/mailbox.c \
src/rpi_ws281x/mailbox.c src/rpi_ws281x/pwm.c src/rpi_ws281x/rpihw.c \
src/rpi_ws281x/pcm.c src/rpi_ws281x/ws2811.c
endif

OBJ = $(SRC:.c=.o)

Expand All @@ -39,11 +30,11 @@ OBJ = $(SRC:.c=.o)
all: priv/rpi_ws281x

%.o: %.c
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<
$(CC) -c $(CFLAGS) -o $@ $<

priv/rpi_ws281x: $(OBJ)
@mkdir -p priv
$(CC) $^ $(ERL_LDFLAGS) $(LDFLAGS) -o $@
$(CC) $^ $(LDFLAGS) -o $@

clean:
rm -f priv/rpi_ws281x src/*.o src/rpi_ws281x/*.o
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Add it to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:nerves_neopixel, "~> 0.4"}]
[{:nerves_neopixel, "~> 1.0"}]
end
```

Expand Down
33 changes: 1 addition & 32 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,34 +1,3 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure for your application as:
#
# config :blinky, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:blinky, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"

config :logger,
compile_time_purge_level: :info

import_config "#{Mix.env}.exs"
19 changes: 19 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use Mix.Config

config :logger, level: :debug

config :nerves_neopixel,
canvas: {8, 4},
channels: [:channel1]

config :nerves_neopixel, :channel1,
pin: 18,
arrangement: [
%{
type: :matrix,
origin: {0, 0},
count: {8, 4},
direction: {:right, :down},
progressive: true
}
]
18 changes: 18 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use Mix.Config

config :logger, level: :warn

config :nerves_neopixel,
canvas: {1, 1},
channels: [:channel1]

config :nerves_neopixel, :channel1,
pin: 18,
arrangement: [
%{
type: :strip,
origin: {0, 0},
count: 1,
direction: :right
}
]
73 changes: 24 additions & 49 deletions lib/nerves/neopixel.ex
Original file line number Diff line number Diff line change
@@ -1,70 +1,45 @@
defmodule Nerves.Neopixel do
@moduledoc false
alias Nerves.Neopixel.HAL

use GenServer
require Logger

def start_link(channel1, channel2 \\ [pin: 0, count: 0]) do
GenServer.start_link(__MODULE__, [channel1, channel2], [name: __MODULE__])
end
@moduledoc """
# `Nerves.Neopixel`
"""

def stop() do
GenServer.stop(__MODULE__)
def set_brightness(channel, brightness) do
GenServer.cast(HAL, {:set_brightness, channel, brightness})
end

# render()
def render({_, _} = data) do
render(0, data)
def set_gamma(channel, gamma) do
GenServer.cast(HAL, {:set_gamma, channel, gamma})
end

def render(channel, {_, _} = data) do
GenServer.call(__MODULE__, {:render, channel, data})
def set_pixel({x, y}, {r, g, b}) do
GenServer.cast(HAL, {:set_pixel, {x, y}, {r, g, b, 0}})
end

def init([ch1, ch2]) do
ch1_pin = (ch1[:pin] || raise "Must pass pin for channel 1")
|> to_string
ch1_count = (ch1[:count] || raise "Must pass count for channel 1")
|> to_string

ch2_pin = (ch2[:pin] || 0)
|> to_string
ch2_count = (ch2[:count] || 0)
|> to_string


port = Port.open({:spawn_executable, rpi_ws281x_path()},
[{:args, [ch1_pin, ch1_count, ch2_pin, ch2_count]},
{:packet, 2},
:use_stdio,
:exit_status,
:binary])
{:ok, %{
port: port
}}
def set_pixel({x, y}, {r, g, b, w}) do
GenServer.cast(HAL, {:set_pixel, {x, y}, {r, g, b, w}})
end

def handle_call({:render, channel, {brightness, data}}, _from, s) do
data = ws2811_brg(data)
payload =
{channel, {brightness, data}}
|> :erlang.term_to_binary
send s.port, {self(), {:command, payload}}
{:reply, :ok, s}
def fill({x, y}, width, height, {r, g, b}) do
GenServer.cast(HAL, {:fill, {x, y}, width, height, {r, g, b, 0}})
end
def fill({x, y}, width, height, {r, g, b, w}) do
GenServer.cast(HAL, {:fill, {x, y}, width, height, {r, g, b, w}})
end

def handle_info {_port, {:exit_status, exit_status}}, state do
{:stop, "rpi_ws281x OS process died with status: #{exit_status}", state}
def copy({xs, ys}, {xd, yd}, width, height) do
GenServer.cast(HAL, {:copy, {xs, ys}, {xd, yd}, width, height})
end

defp ws2811_brg(data) when is_list(data) do
Enum.reduce(data, <<>>, fn({r, g, b}, acc) ->
acc <> <<b :: size(8), r :: size(8), g :: size(8), 0x00 :: size(8)>>
end)
def copy_blit({xs, ys}, {xd, yd}, width, height) do
GenServer.cast(HAL, {:copy_blit, {xs, ys}, {xd, yd}, width, height})
end

defp rpi_ws281x_path do
Path.join(:code.priv_dir(:nerves_neopixel), "rpi_ws281x")
def blit({x, y}, width, height, data) do
GenServer.cast(HAL, {:blit, {x, y}, width, height, data})
end

def render, do: GenServer.cast(HAL, :render)
end
14 changes: 14 additions & 0 deletions lib/nerves/neopixel/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Nerves.Neopixel.Application do
use Application

require Logger

@moduledoc """
# `Nerves.Neopixel.Application`
"""

def start(_type, _args) do
Logger.debug("Nerves.Neopixel.start")
Nerves.Neopixel.Supervisor.start_link([])
end
end
11 changes: 11 additions & 0 deletions lib/nerves/neopixel/canvas.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Nerves.Neopixel.Canvas do
alias Nerves.Neopixel.Canvas

defstruct [
:width,
:height,
]

def load_config({width, height}), do: %Canvas{width: width, height: height}
def load_config(_), do: raise "You must specify :canvas dimensions as {width, height} for :nerves_neopixel"
end
70 changes: 68 additions & 2 deletions lib/nerves/neopixel/channel.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,71 @@
defmodule Nerves.Neopixel.Channel do
alias Nerves.Neopixel.{
Channel,
Matrix,
Strip
}

defstruct [
pin: 0, count: 0, brightness: 0,
data: [], invert: false, type: :ws2811]
pin: 0,
brightness: 255,
gamma: nil,
invert: false,
type: :gbr,
arrangement: nil
]

@valid_types [
:rgb, :rbg, :grb, :gbr, :brg, :bgr,
:rgbw, :rbgw, :grbw, :gbrw, :brgw, :bgrw
]

def load_config(channel_config) do
if is_nil(pwm_channel(channel_config[:pin])), do: raise "Each channel must specify a PWM-capable I/O :pin"

%Channel{
pin: channel_config[:pin],
brightness: Keyword.get(channel_config, :brightness, 255),
gamma: load_gamma(channel_config[:gamma]),
invert: Keyword.get(channel_config, :invert, false),
type: load_channel_type(channel_config[:type]),
arrangement: load_arrangement(channel_config[:arrangement])
}
end

def total_count(%Channel{arrangement: arrangement}) do
Enum.reduce(arrangement, 0, fn (%Strip{count: count}, acc) -> acc + count end)
end

def pwm_channel(%Channel{pin: pin}), do: pwm_channel(pin)
def pwm_channel(pin) when is_number(pin) and pin in [12, 18, 40, 52], do: 1
def pwm_channel(pin) when is_number(pin) and pin in [13, 19, 41, 45, 53], do: 2
def pwm_channel(_), do: nil

defp load_gamma(nil), do: nil
defp load_gamma(gamma_table) when is_list(gamma_table) and length(gamma_table) == 256 do
gamma_table
end
defp load_gamma(_), do: raise "The :gamma on a :channel must be set as a list of 256 8-bit integers"

defp load_channel_type(nil), do: :gbr
defp load_channel_type(type) when type in @valid_types, do: type
defp load_channel_type(_), do: raise "Channel :type must be one of #{inspect @valid_types}"

defp load_arrangement(sections) when is_list(sections) do
sections
|> Enum.map(&load_section/1)
|> List.flatten()
end
defp load_arrangement(_), do: raise "You must configure the :arrangement of pixels in each channel as a list"

defp load_section(%{type: :matrix} = matrix_config) do
matrix_config
|> Matrix.load_config()
|> Matrix.to_strip_list()
end
defp load_section(%{type: :strip} = strip_config) do
strip_config
|> Strip.load_config()
end

end
40 changes: 40 additions & 0 deletions lib/nerves/neopixel/config.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule Nerves.Neopixel.Config do
alias Nerves.Neopixel.{
Canvas,
Channel,
Config
}

defstruct [
:canvas,
:channels
]

def load(), do: load(Application.get_all_env(:nerves_neopixel))
def load(config) do
canvas =
config
|> Keyword.get(:canvas)
|> Canvas.load_config()

channels =
config
|> Keyword.get(:channels)
|> Enum.map(fn name -> Keyword.get(config, name) end)
|> Enum.map(& Channel.load_config/1)

%Config{
canvas: canvas,
channels: validate_channels(channels),
}
end

defp validate_channels(channels) when is_list(channels) do
pwm_channels = Enum.map(channels, & Channel.pwm_channel/1)
if (Enum.dedup(pwm_channels) != pwm_channels) do
raise "Each channel must have a :pin from a different hardware PWM channel"
end
channels
end
defp validate_channels(_), do: raise "You must configure a list of :channels for :nerves_neopixel"
end
Loading

0 comments on commit 535a66c

Please sign in to comment.