Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

First commit

  • Loading branch information...
commit ffda7d0cdabbc14d24227eae10f5b1c3bbe44825 0 parents
@wardbekker authored
5 .gitignore
@@ -0,0 +1,5 @@
+*.plt
+*.beam
+config/database.config
+.DS_Store
+
27 LICENCE.md
@@ -0,0 +1,27 @@
+Copyright (c) 2012 Ward Bekker
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46 README.md
@@ -0,0 +1,46 @@
+Erlang Thinkgear Connector Client
+==========
+
+Developer: Ward Bekker <ward@equanimity.nl>
+
+Description
+----------
+
+An Erlang Client library for the [ThinkGear Socket Protocol](http://developer.neurosky.com/docs/lib/exe/fetch.php?media=app_notes:thinkgear_socket_protocol.pdf) from [NeuroSky](http://neurosky.com/).
+
+###You'll need one of [these](http://store.neurosky.com/products/mindwave-1):
+
+![Fashion!](http://cdn.shopify.com/s/files/1/0031/6882/products/MindWave_large_medium.png)
+
+###Software Requirements
+
+* Erlang R15B+
+* MySQL Server
+* Thinkgear Connector (comes with installation media)
+* An active internet connection as Google Graph API is used for the charting.
+
+###Usage
+
+Create a new MySQL database eg. `mindwave` and run the table creation statements from `table.sql`
+
+Copy the database.config example file and adapt it to your MySQL Server needs:
+
+`cp config/database.config.example config/database.config`
+
+Start up an Erlang shell:
+
+`rebar get-deps compile && ./shell.sh`
+
+Now is a good time to turn on your Mindwave headset.
+
+Inside Erlang shell:
+
+`application:start(mindwave).`
+
+And point your browser to [http://localhost:8000/chart](http://localhost:8000/chart) to see the recorded EGG signals. Manual refreshing is required in this version.
+
+###Example Chart
+
+With a proper signal, the chart should look something like this:
+
+![Chart](https://img.skitch.com/20120316-f7yngmu28b5yq8xmusgabc13r3.jpg)
9 config/connector.config
@@ -0,0 +1,9 @@
+[
+ {connector_hostname, "127.0.0.1"},
+ {connector_port, 13854}
+].
+
+%%% Local variables:
+%%% mode: erlang
+%%% End:
+
11 config/database.config.example
@@ -0,0 +1,11 @@
+[
+ {mysql_host, "localhost"},
+ {mysql_user, "user"},
+ {mysql_password, "password"},
+ {mysql_db, "mindwave"}
+].
+
+%%% Local variables:
+%%% mode: erlang
+%%% End:
+
14 config/webmachine.config
@@ -0,0 +1,14 @@
+[
+ {ip, "0.0.0.0"},
+ {port, 8000},
+ {log_dir, "priv/log"},
+ {dispatch, [
+ {["chart"], chart_resource, []},
+ {["entry"], entry_resource, []}
+ ]
+ }
+].
+
+%%% Local variables:
+%%% mode: erlang
+%%% End:
BIN  images/sample.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 makefile
@@ -0,0 +1,18 @@
+compile:
+ rebar compile
+build-plt:
+ dialyzer --build_plt \
+ -pa deps/webmachine/ebin \
+ -pa deps/mysql/ebin \
+ -pa deps/erlydtl/ebin \
+ -pa deps/parse_trans/ebin \
+ --output_plt mindwave_dialyzer.plt \
+ --apps kernel crypto stdlib sasl inets tools xmerl erts
+
+dialyzer: compile
+ dialyzer --plt mindwave_dialyzer.plt \
+ --src src \
+ -pa deps/webmachine/ebin \
+ -pa deps/mysql/ebin \
+ -pa deps/erlydtl/ebin \
+ -pa deps/parse_trans/ebin
13 rebar.config
@@ -0,0 +1,13 @@
+%% -*- mode: erlang; fill-column: 72; comment-column: 48; -*-
+{sub_dirs, ["rel"]}.
+{deps, [
+ {webmachine, ".*", {git, "git://github.com/basho/webmachine.git", "master"}},
+ {erlydtl, "0.7.0", {git, "git://github.com/OJ/erlydtl.git", "HEAD"}},
+ {mysql, ".*", {git, "git://github.com/dizzyd/erlang-mysql-driver.git", "master"}},
+ {parse_trans, ".*", {git, "git://github.com/esl/parse_trans.git", "master"}}
+ ]
+}.
+{edoc_opts, [{private, false}]}.
+{erl_opts, [debug_info, export_all]}.
+
+{require_otp_vsn, "R15"}.
3  shell.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+rebar compile skip-deps=true && erl -mnesia dir db_data -pa `pwd`/ebin `pwd`/deps/*/ebin -boot start_sasl +P 134217727 -run reloader
143 src/chart_resource.erl
@@ -0,0 +1,143 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% Webmachine resource for displaying the eSense data as line charts.
+%% @end
+-module(chart_resource).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1, to_html/2]).
+
+%% ------------------------------------------------------------------
+%% Header includes
+%% ------------------------------------------------------------------
+
+-include_lib("webmachine/include/webmachine.hrl").
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+init([]) -> {ok, undefined}.
+
+to_html(ReqData, State) ->
+ EndTimestamp = utils:maxESenseTimestamp(),
+ StartTimestamp = EndTimestamp - utils:timestamp({0,1000,0}),
+ Charts = [
+ "\"meditation\"", "\"theta\"", "\"highBeta\"",
+ "\"highGamma\"", "\"attention\"", "\"lowBeta\"",
+ "\"delta\"", "\"highAlpha\"", "\"lowGamma\"",
+ "\"lowAlpha\""
+ ],
+ ChartsData = lists:map(
+ fun(C) -> eSenseData(C, StartTimestamp, EndTimestamp) end,
+ Charts),
+ Data = [{charts, [
+ eSenseData(StartTimestamp, EndTimestamp),
+ blinkData(StartTimestamp, EndTimestamp),
+ poorSignalData(StartTimestamp, EndTimestamp)
+ ]
+ ++
+ ChartsData
+ } ],
+ {ok, Content} = linechart_dtl:render(Data),
+ {Content, ReqData, State}.
+
+%% ------------------------------------------------------------------
+%% private Function Definitions
+%% ------------------------------------------------------------------
+
+-spec blinkData(integer(), integer()) -> list().
+blinkData(StartTimestamp, EndTimestamp) ->
+ DataRows = lists:map(
+ fun( R ) ->
+ {
+ erlang:integer_to_list(lists:nth(1, R)),
+ lists:nth(2, R)
+ }
+ end,
+ utils:blink(StartTimestamp, EndTimestamp)
+ ),
+ Data = [
+ {"hAxis_minValue", StartTimestamp},
+ {"hAxis_maxValue", EndTimestamp},
+ {"width", "1012"},
+ {"height", "200"},
+ {"title", "Blink"},
+ {"columns", [{ "number", "Timestamp"}, { "number", "strength" }]},
+ {"rows", DataRows }
+ ],
+ Data.
+
+-spec poorSignalData(integer(), integer()) -> list().
+poorSignalData(StartTimestamp, EndTimestamp) ->
+ DataRows = lists:map(
+ fun( R ) ->
+ {
+ erlang:integer_to_list(lists:nth(1, R)),
+ lists:nth(2, R)
+ }
+ end,
+ utils:poorSignal(StartTimestamp, EndTimestamp)
+ ),
+ Data = [
+ {"hAxis_minValue", StartTimestamp},
+ {"hAxis_maxValue", EndTimestamp},
+ {"width", "1024"},
+ {"height", "200"},
+ {"title", "Poor Signal"},
+ {"columns", [{ "number", "Timestamp"}, { "number", "level" }]},
+ {"rows", DataRows }
+ ],
+ Data.
+
+-spec eSenseData(integer(), integer()) -> list().
+eSenseData(StartTimestamp, EndTimestamp) ->
+ Res = utils:eSenses(StartTimestamp, EndTimestamp),
+ DataColumns1 = re:split(lists:nth(2,lists:nth(1, Res)), ","),
+ DataColumns2 = [
+ {"number", "Timestamp"}] ++
+ lists:map(fun(H) -> { "number", H } end, DataColumns1),
+ DataRows = lists:map(
+ fun( R ) ->
+ {
+ erlang:integer_to_list(lists:nth(1, R)),
+ lists:nth(3, R)
+ }
+ end, Res),
+
+ Data = [
+ {"hAxis_minValue", StartTimestamp},
+ {"hAxis_maxValue", EndTimestamp},
+ {"width", "1024"},
+ {"height", "200"},
+ {"title", "Combined eSenses"},
+ {"columns", DataColumns2},
+ {"rows", DataRows }
+ ],
+ Data.
+
+-spec eSenseData(iolist(), integer(), integer()) -> list().
+eSenseData(Esense, StartTimestamp, EndTimestamp) ->
+ Res = utils:eSense(Esense, StartTimestamp, EndTimestamp),
+ DataColumns1 = re:split(lists:nth(2,lists:nth(1, Res)), ","),
+ DataColumns2 = [{"number", "Timestamp"}] ++ lists:map(fun(H) -> { "number", H } end, DataColumns1),
+ DataRows = lists:map(
+ fun( R ) ->
+ {
+ erlang:integer_to_list(lists:nth(1, R)),
+ erlang:list_to_integer(erlang:binary_to_list(lists:nth(3, R)))
+ }
+ end, Res),
+ Data = [
+ {"hAxis_minValue", StartTimestamp},
+ {"hAxis_maxValue", EndTimestamp},
+ {"width", "1024"},
+ {"height", "200"},
+ {"title", Esense},
+ {"columns", DataColumns2},
+ {"rows", DataRows }
+ ],
+ Data.
132 src/connection_ser.erl
@@ -0,0 +1,132 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% gen_server TCP client which connects to ThinkGear Connector,
+%% parses the incomping packets and persists them into a database
+%% @end
+-module(connection_ser).
+-behaviour(gen_server).
+-include("mindwave.hrl").
+
+-define(SERVER, ?MODULE).
+-define(CONNECTOR_HOSTNAME, "127.0.0.1").
+-define(CONNECTOR_PORT, 13854).
+-define(
+ CONNECTOR_INIT_MSG,
+ "{\"enableRawOutput\": false, \"format\": \"Json\"}\n"
+).
+-define(SYNC_BYTE, 170).
+-define(EX_CODE_BYTE, 85).
+
+-record(state, { socket }).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export([start_link/1]).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+-spec start_link([]) -> 'ignore' | {'error',_} | {'ok',pid()}.
+start_link(Config) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, Config, []).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Definitions
+%% ------------------------------------------------------------------
+
+-spec init([]) -> {'ok',#state{socket::port()}}.
+init(Config) ->
+ Hostname = proplists:get_value(connector_hostname, Config, "127.0.0.1"),
+ Port = proplists:get_value(connector_port, Config, 13854),
+ {ok, Socket} = gen_tcp:connect(
+ Hostname, Port, [binary, {packet, 0}]
+ ),
+ gen_tcp:send(Socket, ?CONNECTOR_INIT_MSG),
+ State = #state{ socket = Socket},
+ {ok, State}.
+
+handle_call(stop, _From, State) ->
+ {stop, normal, State};
+handle_call(_Request, _From, State) ->
+ {noreply, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({tcp, _Port, Data}, State) ->
+ %% Data can be non-json when init_msg
+ %% is not processed by the connector, exiting the server
+ %% Data can consists of multiple Packets joined with a Return
+ lists:map(
+ fun(Packet) ->
+ persist_packet(mochijson2:decode(Packet))
+ end,
+ re:split(Data, "\r",[{return,list},trim])
+ ),
+ {noreply, State};
+handle_info({tcp_closed, _Socket}, State) ->
+ {stop, normal, State};
+handle_info({tcp_error, _Socket, Reason}, State) ->
+ Reason1 = {tcp_error, Reason},
+ {stop, Reason1, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ gen_tcp:close(State#state.socket),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% ------------------------------------------------------------------
+%% private Function Definitions
+%% ------------------------------------------------------------------
+
+%% @doc parses packet and persist to database storage
+-spec persist_packet({'struct',[{_,_},...]}) -> any().
+persist_packet({struct,[{<<"poorSignalLevel">>,Level}]}) ->
+ PoorSignal = #poorsignal{
+ timestamp = utils:timestamp(erlang:now()), level = Level
+ },
+ database_ser:insert(PoorSignal);
+persist_packet({struct,[{<<"blinkStrength">>,Strength}]}) ->
+ Blink = #blink{
+ timestamp = utils:timestamp(erlang:now()), strength = Strength
+ },
+ database_ser:insert(Blink);
+persist_packet(
+ {struct,[
+ {<<"eSense">>,
+ {struct, EsenseTuples1}
+ },
+ {<<"eegPower">>,
+ {struct, EsenseTuples2}
+ },
+ {<<"poorSignalLevel">>,PoorSignalLevel}
+ ]
+ }) ->
+ Timestamp = utils:timestamp(erlang:now()),
+ PoorSignal = #poorsignal{ timestamp = Timestamp, level = PoorSignalLevel },
+ database_ser:insert(PoorSignal),
+ EsensesDict = dict:from_list(EsenseTuples1 ++ EsenseTuples2),
+ dict:map(fun (E, Strength) ->
+ Esense = #esense{
+ timestamp = Timestamp,
+ name = erlang:binary_to_list(E),
+ strength = Strength
+ },
+ database_ser:insert(Esense)
+ end,
+ EsensesDict
+ ).
140 src/database_ser.erl
@@ -0,0 +1,140 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% singleton gen_server for inserting and fetching records to a MYSQL db.
+%% @end
+-module(database_ser).
+-behaviour(gen_server).
+-include("mindwave.hrl").
+
+-define(SERVER, ?MODULE).
+-define(POOL_ID, 1).
+
+-record(state, {}).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+-export([start_link/1, insert/1, fetch/1, fetch/2]).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+start_link(Config) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, Config, []).
+
+insert(Record) ->
+ Table = erlang:element(1, Record),
+ Fields = records:'#info-'(Table),
+ Values = lists:map(
+ fun(I) -> erlang:element(I, Record) end,
+ lists:seq(2, erlang:length(Fields) + 1)
+ ),
+ Query = replace_param(
+ <<"INSERT INTO ?P (?P) VALUES (?)">>,
+ [Table, Fields, Values]
+ ),
+ {updated, _} = mysql:fetch(?POOL_ID, Query).
+
+fetch(Query) ->
+ fetch(Query, []).
+
+fetch(Query, Params) ->
+ Query1 = replace_param(Query,Params),
+ { data, {mysql_result, _Headers, Values, _ , _ , _ } } =
+ mysql:fetch(?POOL_ID, Query1),
+ Values.
+
+%% ------------------------------------------------------------------
+%% gen_server Function Definitions
+%% ------------------------------------------------------------------
+
+init(Config) ->
+ Host = proplists:get_value(mysql_host, Config, "localhost"),
+ User = proplists:get_value(mysql_user, Config, "root"),
+ Password = proplists:get_value(mysql_password, Config, ""),
+ Database = proplists:get_value(mysql_database, Config, "mindwave"),
+
+ % open first connection
+ mysql:start_link(
+ ?POOL_ID, Host, User, Password, Database
+ ),
+ % open second connection
+ mysql:connect(
+ ?POOL_ID, Host, undefined,
+ User, Password, Database, true
+ ),
+ {ok, #state{}}.
+
+handle_call(stop, _From, State) ->
+ Reason = 'stop requested',
+ {stop, Reason, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% ------------------------------------------------------------------
+%% private Function Definitions
+%% ------------------------------------------------------------------
+
+-spec replace_param(binary(),[any()]) -> binary().
+replace_param(<<"?P", Tail/binary>>, [ Param | NewParams ]) ->
+ SeperatedParam = seperator(Param),
+ ReplacedTail = replace_param(Tail, NewParams),
+ erlang:list_to_binary([SeperatedParam, ReplacedTail]);
+replace_param(<<"?", Tail/binary>>, [ Param | NewParams ]) ->
+ EncodedParam = encode(Param),
+ ReplacedTail = replace_param(Tail, NewParams),
+ erlang:list_to_binary([EncodedParam, ReplacedTail]);
+replace_param(<<Head:8, Tail/binary>>, Params) ->
+ ReplacedTail = replace_param(Tail, Params),
+ erlang:list_to_binary([Head, ReplacedTail]);
+replace_param(_, []) ->
+ <<>>.
+
+-spec encode(any()) -> any().
+encode(Param) when erlang:is_list(Param) ->
+ case io_lib:printable_list(Param) of
+ true ->
+ mysql:encode(Param);
+ false ->
+ string:join(
+ lists:map(fun(P) -> encode(printable(P)) end, Param), ", "
+ )
+ end;
+encode(Param) ->
+ mysql:encode(Param).
+
+-spec seperator(any()) -> iolist().
+seperator(Param) when erlang:is_list(Param) ->
+ case io_lib:printable_list(Param) of
+ true ->
+ Param;
+ false ->
+ string:join(lists:map(fun(P) -> printable(P) end, Param), ", ")
+ end;
+seperator(Param) ->
+ printable(Param).
+
+-spec printable(any()) -> iolist().
+printable(Param) ->
+ lists:flatten(io_lib:format("~p",[Param])).
41 src/entry_resource.erl
@@ -0,0 +1,41 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% Webmachine resource for event entry form
+%% @end
+-module(entry_resource).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1, to_html/2, allowed_methods/2, process_post/2]).
+
+%% ------------------------------------------------------------------
+%% Header includes
+%% ------------------------------------------------------------------
+
+-include_lib("webmachine/include/webmachine.hrl").
+-include("mindwave.hrl").
+
+%% ------------------------------------------------------------------
+%% API Function Definitions
+%% ------------------------------------------------------------------
+
+init([]) -> {ok, undefined}.
+
+allowed_methods(ReqData, State) ->
+ {['GET', 'HEAD', 'POST'], ReqData, State}.
+
+process_post(ReqData, State) ->
+ [{"event", Event}] = mochiweb_util:parse_qs(wrq:req_body(ReqData)),
+ EventRec = #event{
+ timestamp = utils:timestamp(erlang:now()), name = Event
+ },
+ database_ser:insert(EventRec),
+ {true, ReqData, State}.
+
+to_html(ReqData, State) ->
+ Data = [],
+ {ok, Content} = entry_dtl:render(Data),
+ {Content, ReqData, State}.
+
12 src/mindwave.app.src
@@ -0,0 +1,12 @@
+{application, mindwave,
+ [
+ {description, ""},
+ {vsn, "1"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { mindwave_app, []}},
+ {env, []}
+ ]}.
8 src/mindwave.hrl
@@ -0,0 +1,8 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% Definition of records that are shared between modules
+%% @end
+-record(blink, { timestamp, strength }).
+-record(poorsignal, { timestamp, level }).
+-record(esense, { timestamp, name, strength }).
+-record(event, { timestamp, name }).
26 src/mindwave_app.erl
@@ -0,0 +1,26 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% Mindwave OTP app
+%% @end
+-module(mindwave_app).
+-behaviour(application).
+
+%% ------------------------------------------------------------------
+%% application Function Exports
+%% ------------------------------------------------------------------
+
+-export([start/2, stop/1]).
+
+%% ------------------------------------------------------------------
+%% application Function Definitions
+%% ------------------------------------------------------------------
+
+start(_StartType, _StartArgs) ->
+ application:start(crypto),
+ application:start(inets),
+ application:start(mochiweb),
+ application:start(webmachine),
+ mindwave_sup:start_link().
+
+stop(_State) ->
+ ok.
52 src/mindwave_sup.erl
@@ -0,0 +1,52 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% application supervisor
+%% @end
+-module(mindwave_sup).
+-behaviour(supervisor).
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type, Args), {I, {I, start_link, [Args]}, permanent, 5000, Type, [I]}).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export([start_link/0]).
+
+%% ------------------------------------------------------------------
+%% gen_server Function Exports
+%% ------------------------------------------------------------------
+
+-export([init/1]).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ------------------------------------------------------------------
+%% supervisor Function Definitions
+%% ------------------------------------------------------------------
+
+init([]) ->
+ Children = [
+ ?CHILD(database_ser, worker, read_config("database.config")),
+ ?CHILD(connection_ser, worker, read_config("connector.config")),
+ webmachine_child_spec()],
+ {ok, { {one_for_one, 5, 10}, Children} }.
+
+read_config(Filename) ->
+ {ok, [Config]} = file:consult(filename:join(
+ [filename:dirname(code:which(?MODULE)),
+ "..", "config", Filename])),
+ Config.
+
+webmachine_child_spec() ->
+ {webmachine_mochiweb,
+ {
+ webmachine_mochiweb, start, [read_config("webmachine.config")]
+ },
+ permanent, 5000, worker, dynamic
+ }.
9 src/records.erl
@@ -0,0 +1,9 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% provides exprecs utility functions for exported records.
+%% @end
+-module(records).
+-compile({parse_transform, exprecs}).
+-include("mindwave.hrl").
+-export_records([blink, poorsignal, esense, event]).
+
71 src/utils.erl
@@ -0,0 +1,71 @@
+%% @author Ward Bekker <ward@equanimity.nl>
+%% @doc
+%% Database queries
+%% @end
+-module(utils).
+
+%% ------------------------------------------------------------------
+%% API Function Exports
+%% ------------------------------------------------------------------
+
+-export(
+ [eSenses/2, eSense/3, poorSignal/2,
+ blink/2, timestamp/1, time_tuple/1,
+ maxESenseTimestamp/0]
+ ).
+
+-include_lib("stdlib/include/qlc.hrl").
+
+%% ------------------------------------------------------------------
+%% Function Definitions
+%% ------------------------------------------------------------------
+
+maxESenseTimestamp() ->
+ Query = <<"SELECT MAX(`timestamp`) FROM esense">>,
+ [[TimeStamp]] = database_ser:fetch(Query),
+ TimeStamp.
+
+eSenses(StartTimestamp, EndTimestamp) ->
+ database_ser:fetch(
+ <<"SELECT e.`timestamp`, GROUP_CONCAT(e.`name`) as `name`,
+GROUP_CONCAT(e.`strength`) as `strength` FROM esense e
+WHERE e.`timestamp` BETWEEN ? AND ? AND e.`timestamp` NOT IN
+(SELECT timestamp FROM poorsignal WHERE level > 10)
+GROUP BY e.`timestamp` ORDER BY e.`timestamp`">>,
+ [StartTimestamp, EndTimestamp]
+ ).
+
+eSense(Name, StartTimestamp, EndTimestamp) ->
+ database_ser:fetch(
+ <<"SELECT e.`timestamp`, GROUP_CONCAT(e.`name`) as `name`,
+GROUP_CONCAT(e.`strength`) as `strength` FROM esense e
+WHERE e.`name` = ? AND e.`timestamp` BETWEEN ? AND ? AND e.`timestamp` NOT IN
+(SELECT timestamp FROM poorsignal WHERE level > 10)
+GROUP BY e.`timestamp` ORDER BY e.`timestamp`">>,
+ [Name, StartTimestamp, EndTimestamp]
+ ).
+
+poorSignal(StartTimestamp, EndTimestamp) ->
+ Query = <<"SELECT `timestamp`, `level` FROM `poorsignal`",
+ "WHERE `timestamp` BETWEEN ? AND ?">>,
+ database_ser:fetch(Query, [StartTimestamp, EndTimestamp]).
+
+blink(StartTimestamp, EndTimestamp)->
+ Query = <<"SELECT `timestamp`, `strength` FROM `blink` ",
+ "WHERE `timestamp` BETWEEN ? AND ?">>,
+ database_ser:fetch(Query, [StartTimestamp, EndTimestamp]).
+
+event(StartTimestamp, EndTimestamp)->
+ Query = <<"SELECT `timestamp`, `name` FROM `event`",
+ "WHERE `timestamp` BETWEEN ? AND ?">>,
+ database_ser:fetch(Query, [StartTimestamp, EndTimestamp]).
+
+timestamp({Mega, Secs, Micro}) ->
+ Mega*1000*1000*1000*1000 + Secs * 1000 * 1000 + Micro.
+
+time_tuple(Timestamp) ->
+ {
+ Timestamp div 1000000000000,
+ Timestamp div 1000000 rem 1000000,
+ Timestamp rem 1000000
+ }.
42 tables.sql
@@ -0,0 +1,42 @@
+-- Create syntax for TABLE 'blink'
+CREATE TABLE `blink` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `timestamp` bigint(20) DEFAULT NULL,
+ `strength` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `timestamp` (`timestamp`),
+ KEY `strength` (`strength`)
+) ENGINE=MyISAM;
+
+-- Create syntax for TABLE 'esense'
+CREATE TABLE `esense` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `timestamp` bigint(20) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `strength` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `timestamp` (`timestamp`),
+ KEY `strength` (`strength`),
+ KEY `name` (`name`)
+) ENGINE=MyISAM;
+
+-- Create syntax for TABLE 'event'
+CREATE TABLE `event` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `timestamp` bigint(20) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `timestamp` (`timestamp`),
+ KEY `name` (`name`)
+) ENGINE=MyISAM;
+
+-- Create syntax for TABLE 'poorsignal'
+CREATE TABLE `poorsignal` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `timestamp` bigint(255) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `timestamp` (`timestamp`),
+ KEY `level` (`level`)
+) ENGINE=MyISAM;
+
14 templates/entry.dtl
@@ -0,0 +1,14 @@
+<html>
+ <head>
+ <title>Entry</title>
+ </head>
+ <body>
+ <form method="POST" >
+ <input type="submit" name="event" value="distracted" />
+ <input type="submit" name="event" value="day_dreaming" />
+ <input type="submit" name="event" value="worrying" />
+ <input type="submit" name="event" value="concentrated" />
+ <input type="submit" name="event" value="stressed" />
+ </form>
+ </body>
+</html>
42 templates/linechart.dtl
@@ -0,0 +1,42 @@
+<html>
+ <head>
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+ <script type="text/javascript">
+ google.load("visualization", "1", {packages:["corechart"]});
+ google.setOnLoadCallback(drawCharts);
+ function drawCharts() {
+
+ {% for chart in charts %}
+
+ var data = new google.visualization.DataTable();
+
+ {% for type, label in chart.columns %}
+ data.addColumn('{{ type }}', '{{ label }}');
+ {% endfor %}
+
+ data.addRows([
+ {% for label, values in chart.rows %}
+ [{{ label }}, {{ values }}],
+ {% endfor %}
+ ]);
+
+ var options = {
+ width: {{ chart.width }}, height: {{ chart.height }} ,
+ title: '{{ chart.title }}',
+ hAxis: { viewWindowMode: 'explicit', viewWindow: { min: {{chart.hAxis_minValue}}, max: {{chart.hAxis_maxValue}}} },
+ interpolateNulls: true
+ };
+
+ var chart = new google.visualization.LineChart(document.getElementById('chart_div{{forloop.counter0 }}'));
+ chart.draw(data, options);
+
+ {% endfor %}
+ }
+ </script>
+ </head>
+ <body>
+ {% for chart in charts %}
+ <div id="chart_div{{forloop.counter0 }}"></div>
+ {% endfor %}
+ </body>
+</html>
Please sign in to comment.
Something went wrong with that request. Please try again.