Skip to content

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
% 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
% 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"
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)
{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)
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]),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.