And, if you want to load code to the Arduino directly from Erlang, try this: stk500)
First we'll need to upload a sketch to the Arduino. There are many examples floating around. Here is a simple one to control LED's. Yes, not exactly novel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define DEBUG 0 | |
#define FPIN 8 // first Pin | |
#define LPIN 13 // last Pin | |
void setup() { | |
int i = 0; | |
Serial.begin(9600); | |
for (i = FPIN; i <= LPIN; i++) | |
pinMode(i, OUTPUT); | |
} | |
void loop() { | |
int val = 0; | |
int inb = 0; | |
if (Serial.available() > 0) { | |
inb = Serial.read(); | |
#ifdef DEBUG | |
// for debugging from the serial monitor | |
if ( (inb >= '0') && (inb <= '9')) | |
inb -= '0'; | |
#endif /* DEBUG */ | |
for (val = 0; val <= LPIN - FPIN; val++) | |
digitalWrite(val + FPIN, ( (inb & (1 << val)) ? HIGH : LOW)); | |
} | |
} |
You will need to modify the definitions of FPIN and LPIN to match the first and last digital pins the LED's are plugged into. Compile and upload the sketch.
The lights are set as a bitmask. For example, to activate the first LED, send the integer 1; for the third LED, send 4; and for both the first and third LED, send 5. If you defined the DEBUG macro, you can test from the serial monitor by sending the ASCII representation of the numbers.
Now download and compile the erlang-serial port driver. Start up Erlang:
1> Pid = serial:start([{open, "/dev/ttyUSB0"}, {speed, 9600}]). <0.47.0> 2> Pid ! {send, 1 bsl 2}. % LED at position 3
Doing Something Useful
Switching LED's on and off programmatically is hours of fun. But, like many others have discovered before, hook it up to a monitoring system and it becomes sort of useful.
I have a monitoring service running Nagios. Yeah, I think Nagios sucks too. The Erlang code does a request for the tactical overview page, parses out the statistics and sets the alert status accordingly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(health). | |
-behaviour(gen_server). | |
-export([start_link/0, | |
check/0, alert/0, dump/0, | |
threshold/1, poll/1, url/1 | |
]). | |
-export([init/1, handle_call/3]). | |
-define(SERVER, ?MODULE). | |
-define(URL, "http://YOUR_HOSTNAME_HERE/nagios/cgi-bin/tac.cgi"). | |
-define(LED, [ | |
{critical, 1 bsl 2}, | |
{warning, 1 bsl 3}, | |
{unknown, 1 bsl 3}, | |
{ok, 1 bsl 4}, | |
{error, (1 bsl 5) - 1} | |
] | |
). | |
-record(threshold, { | |
critical = 0, | |
warning = 0, | |
unknown = 0 | |
}). | |
-record(status, { | |
critical = 0, | |
warning = 0, | |
unknown = 0, | |
ok = 0 | |
}). | |
-record(state, { | |
poll = false, | |
url = ?URL, | |
interval = 60 * 1000, % 1 minute | |
threshold = #threshold{}, | |
serial, | |
current = ok, | |
status = #status{} | |
}). | |
alert() -> | |
gen_server:call(?SERVER, alert). | |
check() -> | |
gen_server:call(?SERVER, check). | |
dump() -> | |
gen_server:call(?SERVER, dump). | |
poll(true) -> | |
gen_server:call(?SERVER, {poll, true}), | |
health:check(), | |
health:alert(); | |
poll(false) -> | |
gen_server:call(?SERVER, {poll, false}). | |
threshold(N) when tuple_size(N) =:= 3 -> | |
gen_server:call(?SERVER, #threshold{ | |
critical = element(1,N), | |
warning = element(2,N), | |
unknown = element(3,N) | |
}). | |
url(N) when is_list(N) -> | |
gen_server:call(?SERVER, {url, N}). | |
start_link() -> | |
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). | |
init([]) -> | |
inets:start(), | |
Pid = serial:start([{open, "/dev/ttyUSB0"}, {speed, 9600}]), | |
{ok, #state{serial = Pid, poll = true}}. | |
handle_call(check, _From, #state{interval = T} = State) -> | |
Status = try fetch(State) of | |
N -> | |
interval(State, ?SERVER, check), | |
N | |
catch | |
_:_ -> | |
interval(State#state{interval = T div 4}, ?SERVER, check), | |
error | |
end, | |
{reply, ok, State#state{status = Status}}; | |
handle_call(alert, _From, State) -> | |
Status = alert(State), | |
led(Status, State), | |
interval(State, ?SERVER, alert), | |
{reply, ok, State#state{current = Status}}; | |
handle_call(#threshold{} = N, _From, State) -> | |
{reply, ok, State#state{threshold = N}}; | |
handle_call(dump, _From, State) -> | |
{reply, State, State}; | |
handle_call({poll, N}, _From, State) -> | |
{reply, ok, State#state{poll = N}}; | |
handle_call({url, N}, _From, State) -> | |
{reply, ok, State#state{url = N}}. | |
%% | |
%% Internal | |
%% | |
fetch(#state{url = URL}) -> | |
{ok, {{"HTTP/1.1",200,"OK"}, _Headers, Body}} = httpc:request(URL), | |
{match, Crit} = re:run(Body, "(\\d+)\\sCritical", [{capture, [1], binary}]), | |
{match, Warn} = re:run(Body, "(\\d+)\\sWarning", [{capture, [1], binary}]), | |
{match, Unk} = re:run(Body, "(\\d+)\\sUnknown", [{capture, [1], binary}]), | |
{match, Ok} = re:run(Body, "(\\d+)\\sOk", [{capture, [1], binary}]), | |
#status{ | |
critical = b2i(Crit), | |
warning = b2i(Warn), | |
unknown = b2i(Unk), | |
ok = b2i(Ok) | |
}. | |
alert(#state{status = error}) -> | |
error; | |
alert(#state{status = S, threshold = T}) -> | |
Crit = S#status.critical, | |
Warn = S#status.warning, | |
Unk = S#status.unknown, | |
error_logger:info_report([ | |
{crit, Crit, T#threshold.critical}, | |
{warn, Warn, T#threshold.warning}, | |
{unk, Unk, T#threshold.unknown} | |
]), | |
Status = try | |
threshold(critical, Crit, T#threshold.critical), | |
threshold(warning, Warn, T#threshold.warning), | |
threshold(unknown, Unk, T#threshold.unknown) of | |
_ -> ok | |
catch | |
throw:N -> N | |
end, | |
error_logger:info_report([{status, Status}]), | |
Status. | |
threshold(critical,N,T) when N > T -> throw(critical); | |
threshold(warning,N,T) when N > T -> throw(warning); | |
threshold(unknown,N,T) when N > T -> throw(unknown); | |
threshold(_,_,_) -> ok. | |
led(Status, #state{current = Status}) -> | |
ok; | |
led(Status, #state{serial = Serial}) -> | |
error_logger:info_report([{led, proplists:get_value(Status, ?LED)}]), | |
Serial ! {send, proplists:get_value(Status, ?LED)}, | |
ok. | |
b2i([N]) when is_binary(N) -> | |
list_to_integer(binary_to_list(N)). | |
interval(State, M, F) -> | |
interval(State, M, F, []). | |
interval(#state{poll = true, interval = T}, M, F, A) -> | |
timer:apply_after(T, M, F, A); | |
interval(_, _, _, _) -> | |
ok. | |
Monitoring Ambient Light
Here is another simple project: creating a graph of ambient light levels using an LDR and the really quite awesome eplot library.
Connect the LDR to an analog pin and upload a sketch:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int LDR = 2; | |
int val = 0; | |
int inb = 0; | |
void setup() { | |
Serial.begin(9600); | |
pinMode(LDR, INPUT); | |
} | |
void loop() { | |
if (Serial.available() > 0) { | |
inb = Serial.read(); | |
if (inb == '1') { | |
val = analogRead(LDR); | |
//Serial.println(val); | |
Serial.print(val); | |
} | |
} | |
} |
light:start(). sensor:start().Reading the sensor:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(light). | |
-export([start/0,start/1]). | |
-define(TABLE, sensor). | |
-define(STORE, "db/sensor.db"). | |
-define(TTY, "/dev/ttyUSB0"). | |
-define(LDR, $1). | |
-define(INTRVL, 1000). | |
start() -> | |
start([]). | |
start(Options) -> | |
dets:open_file(?TABLE, [{type, set}, {file, ?STORE}]), | |
TTY = proplists:get_value(tty, Options, ?TTY), | |
Interval = proplists:get_value(interval, Options, ?INTRVL), | |
Speed = proplists:get_value(speed, Options, 9600), | |
LDR = proplists:get_value(ldr, Options, ?LDR), | |
Pid = serial:start([{open, TTY}, {speed, Speed}]), | |
timer:send_interval(Interval, Pid, {send, LDR}), | |
loop(Pid). | |
loop(Pid) -> | |
receive | |
{data, N} when is_binary(N) -> | |
%io:format("~w~n", [binary_to_integer(N)]), | |
dets:insert(?TABLE, {erlang:now(), binary_to_integer(N)}), | |
loop(Pid); | |
Error -> | |
io:format("error:~p~n", [Error]) | |
end. | |
binary_to_integer(B) when is_binary(B) -> | |
list_to_integer(binary_to_list(B)). | |
Displaying the graph:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(sensor). | |
-include("light.hrl"). | |
-export([start/0,stop/0]). | |
% web interface | |
-export([graph/3]). | |
start() -> | |
dets:open_file(?TABLE, [{file, ?STORE}]), | |
application:start(inets), | |
inets:start(httpd, [ | |
{modules, [ | |
mod_alias, | |
mod_auth, | |
mod_esi, | |
mod_actions, | |
mod_cgi, | |
mod_dir, | |
mod_get, | |
mod_head, | |
mod_log, | |
mod_disk_log | |
]}, | |
{port, 8889}, | |
{server_name, "foo"}, | |
{server_root, "log"}, | |
{document_root, "www"}, | |
{directory_index, ["index.html"]}, | |
{error_log, "error.log"}, | |
{security_log, "security.log"}, | |
{transfer_log, "transfer.log"}, | |
{erl_script_alias, {"/web", [sensor]}} | |
]). | |
stop() -> | |
inets:stop(). | |
%%% | |
%%% web services | |
%%% | |
graph(SessionID, _Env, _Input) -> | |
mod_esi:deliver(SessionID, header(png)), | |
B = generate_graph(), | |
mod_esi:deliver(SessionID, binary_to_list(B)). | |
%%% | |
%%% helper functions | |
%%% | |
generate_graph() -> | |
L = get_values(), | |
egd_chart:graph([{"light",L}], [ | |
{bg_rgba, {255,255,255,255}}, | |
{width, 1024}, | |
{height, 800} | |
]). | |
header(png) -> | |
"Content-Type: image/png\r\n\r\n". | |
get_values() -> | |
L = lists:sort([ | |
{calendar:datetime_to_gregorian_seconds(calendar:now_to_universal_time(K)), V} | |
|| [{K, V}] <- dets:match(sensor, '$1') ]), | |
[ {K - element(1,hd(L)), V} || {K,V} <- L ]. |
After the web server is running, the web page can be found by going to:
http://localhost:8889/web/sensor:graph
(I think the spikiness was caused by a blinking LED somewhere around the LDR).
Fabulous example!!! Thanks, help me a lot!!
ReplyDelete