Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
%%% led_controller is a module to toggle LEDs wired to gpio pins on the raspberry pi
-module(led_controller).
-behavior(gen_server).
-export([start_link/1]).
% standard gen_server
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
% Public Interface
-export([on/1, off/1, blink/2]).
% Don't call these directly
-export([blink_cast/2]).
% orddicts, Color atoms to Pins, Color atom to TRef, Color atom to state (on/off) not blink
-record(state, {led_pins=[], blinking_led=[], led_state=[]}).
% Time between on/off state changes
-define(BLINK_DELAY, 250).
%% gen_server specfic
%% Pass in proplist of {Color, GPIO_Pin} Example: {red, 17}
start_link(Color2Pin) ->
gen_server:start_link({local, led_svc}, ?MODULE, Color2Pin, []).
init(Color2Pin) ->
%Color2Pin = [{red, 22}, {green, 17}],
%% Know when parent shuts down
process_flag(trap_exit, true),
io:format("[~s] started.~n", [?MODULE]),
% Init state of LEDs to off
% Convert our proplist to orddict
Color2PinDict = orddict:from_list(Color2Pin),
% Init all LEDs to off
LedState = orddict:map(fun(_X, _Y) -> off end, Color2PinDict),
{ok, #state{led_pins=Color2PinDict, led_state=LedState}}.
%% Public Interface
%% Turn Color LED ON
on(Color) ->
toggle(Color, on).
%% Turn Color LED OFF
off(Color) ->
toggle(Color, off).
toggle(Color, LedState) ->
gen_server:cast(led_svc, {LedState, Color}).
%% Blink Color LED Times
blink(Color, Times) ->
% A blink is 1 on/off cycle, we translate this to state changes
StateChanges = Times * 2,
blink_cast(Color, StateChanges).
%% Private
%% Used to turn an led on and off
toggle(Color, Pin, LedState) ->
io:format("[~s] ~p ~p~n", [?MODULE, Color, LedState]),
File = get_value_file(Pin),
case LedState of
on -> Value = "1";
off -> Value = "0"
end,
ok = file:write_file(File, Value ++ "\n").
%% Gets called by public interface and via apply_after
blink_cast(Color, StateChangesLeft) ->
gen_server:cast(led_svc, {blink, Color, StateChangesLeft}).
% 0 state changes means we're done
blink(_Color, _Pin, StateChangesLeft) when StateChangesLeft =< 0 -> done;
blink(Color, Pin, StateChangesLeft) ->
io:format("[~s] Blink ~p ~p~n", [?MODULE, Color, StateChangesLeft]),
% On even states on, odd Off
case StateChangesLeft rem 2 of
0 -> toggle(Color, Pin, on);
_ -> toggle(Color, Pin, off)
end,
{ok, _TRef} = timer:apply_after(?BLINK_DELAY, ?MODULE, blink_cast, [Color, StateChangesLeft - 1]).
%% Return the file that we are writing to to toggle value
get_value_file(Pin) ->
"/sys/class/gpio/gpio" ++ integer_to_list(Pin) ++ "/value".
% Get the pin number for a color atom
get_pin_for_color(Color, Color2Pin) ->
orddict:fetch(Color, Color2Pin).
% Remove TRef and cancel apply timer if it exists
cancel_blink(Color, Blinking2TRef) ->
case orddict:find(Color, Blinking2TRef) of
error -> ok;
{ok, TRef} -> timer:cancel(TRef)
end,
orddict:erase(Color, Blinking2TRef).
%% Event Loop
handle_cast({blink, Color, 0}, S = #state{blinking_led=BlinkingLed2TRef, led_state=TrackLedState}) ->
% No more blinks, clear our timer ref
NewBlinkingLed2TRef = cancel_blink(Color, BlinkingLed2TRef),
% Now that the blinking is done, restore to our previous steady state
PrevState = orddict:fetch(Color, TrackLedState),
toggle(Color, PrevState),
{noreply, S#state{blinking_led=NewBlinkingLed2TRef}};
handle_cast({blink, Color, StateChangesLeft}, S = #state{led_pins=Color2Pin, blinking_led=BlinkingLed2TRef}) ->
Pin = get_pin_for_color(Color, Color2Pin),
{ok, TRef} = blink(Color, Pin, StateChangesLeft),
NewBlinkingLed2TRef = orddict:store(Color, TRef, BlinkingLed2TRef),
{noreply, S#state{blinking_led=NewBlinkingLed2TRef}};
%% LedState should be either on or off
handle_cast({LedState, Color}, S = #state{led_pins=Color2Pin, blinking_led=BlinkingLed2TRef, led_state=TrackLedState}) ->
Pin = get_pin_for_color(Color, Color2Pin),
% Cancel blinking in case we were blinking
NewBlinkingLed2TRef = cancel_blink(Color, BlinkingLed2TRef),
toggle(Color, Pin, LedState),
NewTrackLedState = orddict:store(Color, LedState, TrackLedState),
{noreply, S#state{blinking_led=NewBlinkingLed2TRef, led_state=NewTrackLedState}};
handle_cast(_Msg, S) ->
{noreply, S}.
handle_info(Msg, S) ->
io:format("[~s] Unknown info ~p~n", [?MODULE, Msg]),
{noreply, S}.
handle_call(terminate, _From, S) ->
{stop, normal, ok, S};
handle_call(Msg, From, S = #state{}) ->
io:format("[~s] Unknown call from ~p message ~p~n", [?MODULE, From, Msg]),
{noreply, S}.
code_change(_OldVsn, S, _Extra) ->
{ok, S}.
terminate(Reason, _S) ->
io:format("[~s] Terminate Reason: ~p.~n", [?MODULE, Reason]),
ok.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment