diff options
-rw-r--r-- | backend.config | 147 | ||||
-rw-r--r-- | components/dlink_bt/LICENSE | 354 | ||||
-rw-r--r-- | components/dlink_bt/Makefile | 40 | ||||
-rw-r--r-- | components/dlink_bt/README.md | 2 | ||||
-rw-r--r-- | components/dlink_bt/src/bt_connection.erl | 322 | ||||
-rw-r--r-- | components/dlink_bt/src/bt_connection_manager.erl | 266 | ||||
-rw-r--r-- | components/dlink_bt/src/bt_gen_nb_server.erl | 205 | ||||
-rw-r--r-- | components/dlink_bt/src/bt_listener.erl | 93 | ||||
-rw-r--r-- | components/dlink_bt/src/dlink_bt.app.src | 23 | ||||
-rw-r--r-- | components/dlink_bt/src/dlink_bt_app.erl | 39 | ||||
-rw-r--r-- | components/dlink_bt/src/dlink_bt_rpc.erl | 680 | ||||
-rw-r--r-- | components/dlink_bt/src/dlink_bt_sup.erl | 39 | ||||
-rw-r--r-- | components/dlink_tcp/src/dlink_tcp_rpc.erl | 4 | ||||
-rw-r--r-- | components/rvi_common/include/rvi_common.hrl | 2 | ||||
-rw-r--r-- | rebar.config | 2 | ||||
-rw-r--r-- | rvi_backend.config | 2 | ||||
-rw-r--r-- | rvi_sample.config | 2 | ||||
-rw-r--r-- | src/rvi.app.src | 2 | ||||
-rw-r--r-- | tizen.config | 2 |
19 files changed, 2145 insertions, 81 deletions
diff --git a/backend.config b/backend.config index d203f68..38b3ff4 100644 --- a/backend.config +++ b/backend.config @@ -47,8 +47,10 @@ service_discovery, authorize, schedule, - data_link_bert_rpc, - protocol ]}, + dlink_tcp, + bt, + dlink_bt, + proto_bert ]}, %% %% Custom environment settings @@ -63,7 +65,7 @@ [ { handlers, %% Change this to debug, info, notice, warning, or error in %% order to lower the console chatter. - [ {lager_console_backend, notice} ] + [ {lager_console_backend, info} ] } ] }, @@ -135,30 +137,14 @@ { routing_rules, [ - %% Service name prefix that rules are specified for - { "jlr.com/backend/", - %% Which protocol and data link pair to use when transmitting the message - %% to the targeted service. If a pair reports a failure, the next pair is tried. + %% Make sure to have a default if you don't want your message + %% to error out immediately. With a default the message will + %% be queued until it times out, waiting for a remote node + %% to connect and announce that it can handle the targeted service. + { "", [ - { bert_rpc, wifi }, - { bert_rpc, device_3g }, - { bert_rpc, device_sms }, - { joynr, wifi }, - { joynr, device_sms } + { proto_bert_rpc, dlink_tcp_rpc} ] - }, - - %% Used to communicate with vehicles - { "jlr.com/vin/", - { bert_rpc, wifi }, - %% server_3g is augmented with hinting, provided to - { bert_rpc, { server_3g, [ initiate_outbound ]} }, - - %% Protocols can have hinting as well. - %% In this case bert_rpc should only be used if the - %% resulting message size can fit in an SMS (140 bytes). - - { { bert_rpc, [ { max_msg_size, 140 } ] } , server_sms } } ] }, @@ -194,73 +180,84 @@ %% by bert_rpc_server). %% - { service_edge, - %% This is the URL that local services use to connect to - %% the RVI system. It is also used by - %% the other components below to send inter-compoonent - %% JSON-RPC calls to the service edge. - %% - %% For this component url and exo_http_opts should always be specified - %% so that local services have a HTTP port to connect to. - %% - %% gen_server can also be specified to enable faster, native erlang - %% inter compojnent communication. - %% - %% The host and address given in URL should route to the port given - %% in exo_http_opts below. - %% - %% The web socket integrates with the rvi.js javascript code - %% that connects Tizen IVI to the RVI system. + {service_edge, [ - { gen_server, service_edge_rpc }, - { url, "http://127.0.0.1:8801" }, - { exo_http_opts, [ { port, 8801 } ] }, - { websocket, [ { port, 8808}]} + %% Service_edge_rpc component is used as a gen_server + { service_edge_rpc, gen_server, + [ + %% JSON-RPC address will be translated to + %% an URL looking like this: + %% http://127.0.0.1:8801 + %% + %% This URL is used both for communication with + %% locally connected services and for intra-component + %% communication in case the access method for + %% service_edge_rpc is specified as json_rpc. + { json_rpc_address, { "127.0.0.1", 8801 } }, + + %% Websocket is used for websocket access, preferably + %% through the rvi.js package available for Javascript + %% apps in browsers and crosswalk who wants to interface + %% RVI. + { websocket, [ { port, 8808}]} + ] + } ] }, { service_discovery, - [ - %% In this sample file, we turn on both JSON-RPC and - %% gen_server calls. Normally, one of the two are - %% commented out. - { gen_server, service_discovery_rpc }, - { url, "http://127.0.0.1:8802" }, - { exo_http_opts, [ { port, 8802 } ] } + [ { service_discovery_rpc, gen_server, + [ + { json_rpc_address, { "127.0.0.1", 8802 }} + ] + } ] }, { schedule, - [ - %% For obscure reasons, the gen_server component - %% here is just called schedule, not schedule_rpc - { gen_server, schedule }, - { url, "http://127.0.0.1:8803" }, - { exo_http_opts, [ { port, 8803 } ] } + [ { schedule_rpc, json_rpc, + [ + { json_rpc_address, { "127.0.0.1", 8803 }} + ] + } ] }, { authorize, - [ - { gen_server, authorize_rpc }, - { url, "http://127.0.0.1:8804" }, - { exo_http_opts, [ { port, 8804 } ] } + [ { authorize_rpc, gen_server, + [ + { json_rpc_address, { "127.0.0.1", 8804 } } + ] + } ] }, { protocol, - [ - { gen_server, protocol_rpc }, - { url, "http://127.0.0.1:8805" }, - { exo_http_opts, [ { port, 8805 } ] } + [ { proto_bert_rpc, gen_server, + [ + { json_rpc_address, { "127.0.0.1", 8805 } } + ] + } ] }, { data_link, [ - { gen_server, data_link_bert_rpc_rpc }, - { url, "http://127.0.0.1:8806" }, - { exo_http_opts, [ { port, 8806 } ] }, - - %% The bert_rpc_server port will be used to listen to incoming - %% traffic from remote nodes. - %% Must match the port given in node_address - { bert_rpc_server, [ {port, 8807 }]} + { dlink_tcp_rpc, gen_server, + [ + { json_rpc_address, { "127.0.0.1", 8806 } }, + %% Bert_rpc server specifies the port we should + %% listen to for incoming connections + %% from other rvi nodes. + %% A specific NIC address can also be specified + %% through the {ip, "192.168.0.1" } tuple. + { server_opts, [ { port, 8807 }]} + %% { persistent_connections, [ "38.129.64.13:8807" ]} + ] + }, + { dlink_bt_rpc, gen_server, + [ + { json_rpc_address, { "127.0.0.1", 8809 } }, + %% Bert_rpc server specifies the + { server_opts, [ { channel, 1 }]} + ] + } + ] } ] diff --git a/components/dlink_bt/LICENSE b/components/dlink_bt/LICENSE new file mode 100644 index 0000000..c33dcc7 --- /dev/null +++ b/components/dlink_bt/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/components/dlink_bt/Makefile b/components/dlink_bt/Makefile new file mode 100644 index 0000000..9002c05 --- /dev/null +++ b/components/dlink_bt/Makefile @@ -0,0 +1,40 @@ +.PHONY: all deps compile setup clean doc + + +NAME=dlink_bluetooth +export KVDB_BACKENDS=ets + +SETUP_GEN=$(shell ./find_setup_gen.sh) + +all: deps compile + +deps: + rebar get-deps + +compile: + rebar compile + +recomp: + rebar compile skip_deps=true + +setup: + ERL_LIBS=$(PWD)/deps:$(ERL_LIBS):$(PWD) \ + $(SETUP_GEN) $(NAME) priv/setup.config setup + +target: + ERL_LIBS=$(PWD)/deps:$(ERL_LIBS) \ + $(SETUP_GEN) $(NAME) priv/setup.config setup -pz $(PWD)/ebin \ + -target rel -vsn 0.1 + +run: setup + erl -boot setup/start -config setup/sys + +doc: + REBAR_DOC=1 rebar skip_deps=true get-deps doc + +clean: + rebar clean + + + + diff --git a/components/dlink_bt/README.md b/components/dlink_bt/README.md new file mode 100644 index 0000000..59bc25d --- /dev/null +++ b/components/dlink_bt/README.md @@ -0,0 +1,2 @@ +# Bluetooth +# diff --git a/components/dlink_bt/src/bt_connection.erl b/components/dlink_bt/src/bt_connection.erl new file mode 100644 index 0000000..c57b108 --- /dev/null +++ b/components/dlink_bt/src/bt_connection.erl @@ -0,0 +1,322 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% + +%%%------------------------------------------------------------------- +%%% @author magnus <magnus@t520.home> +%%% @copyright (C) 2014, magnus +%%% @doc +%%% +%%% @end +%%% Created : 12 Sep 2014 by magnus <magnus@t520.home> +%%%------------------------------------------------------------------- +-module(bt_connection). + +-behaviour(gen_server). +-include_lib("lager/include/log.hrl"). + +%% API + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([setup/6]). +-export([accept/6]). +-export([send/2]). +-export([send/3]). +-export([is_connection_up/1]). +-export([is_connection_up/2]). +-export([terminate_connection/1]). +-export([terminate_connection/2]). + + +-define(SERVER, ?MODULE). + +-record(st, { + remote_addr = "00:00:00:00:00:00", + channel = 0, + parent_pid = undefined, + rfcomm_ref = undefined, + mod = undefined, + func = undefined, + args = undefined + }). + +%%%=================================================================== +%%% API +%%%=================================================================== +%% MFA is to deliver data received on the socket. + +setup(Addr, Channel, Ref, Mod, Fun, Arg) -> + gen_server:start_link(?MODULE, + {connect, Addr, Channel, Ref, Mod, Fun, Arg }, + []). + +accept(Channel, ListenRef, ParentPid, Mod, Fun, Arg) -> + gen_server:start_link(?MODULE, {accept, + Channel, + ListenRef, + ParentPid, + Mod, + Fun, + Arg},[]). + +send(Pid, Data) when is_pid(Pid) -> + gen_server:cast(Pid, {send, Data}). + +send(Addr, Channel, Data) -> + case connection_manager:find_connection_by_address(Addr, Channel) of + {ok, Pid} -> + gen_server:cast(Pid, {send, Data}); + + _Err -> + ?info("connection:send(): Connection ~p:~p not found for data: ~p", + [ Addr, Channel, Data]), + not_found + + end. + +terminate_connection(Pid) when is_pid(Pid) -> + gen_server:call(Pid, terminate_connection). + +terminate_connection(Addr, Channel) -> + case connection_manager:find_connection_by_address(Addr, Channel) of + {ok, Pid} -> + gen_server:call(Pid, terminate_connection); + + _Err -> not_found + end. + + +is_connection_up(Pid) when is_pid(Pid) -> + is_process_alive(Pid). + +is_connection_up(Addr, Channel) -> + case connection_manager:find_connection_by_address(Addr, Channel) of + {ok, Pid} -> + is_connection_up(Pid); + + _Err -> + false + end. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initializes the server +%% +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @end +%%-------------------------------------------------------------------- +%% MFA used to handle socket closed, socket error and received data +%% When data is received, a separate process is spawned to handle +%% the MFA invocation. +init({connect, BTAddr, Channel, ConnRef, Mod, Fun, Arg}) -> + connection_manager:add_connection(BTAddr, Channel, self()), + + ?debug("connection:init(): self(): ~p", [self()]), + ?debug("connection:init(): BTAddr: ~p", [BTAddr]), + ?debug("connection:init(): Channel: ~p", [Channel]), + ?debug("connection:init(): Ref: ~p", [ConnRef]), + ?debug("connection:init(): Module: ~p", [Mod]), + ?debug("connection:init(): Function: ~p", [Fun]), + ?debug("connection:init(): Arg: ~p", [Arg]), + + %% Grab socket control + {ok, #st{ + remote_addr = BTAddr, + channel = Channel, + rfcomm_ref = ConnRef, + mod = Mod, + func = Fun, + args = Arg + }}; + + + +init({accept, Channel, ListenRef, ParentPid, Mod, Fun, Arg}) -> + { ok, ARef } = rfcomm:accept(ListenRef, infinity, self()), + ?debug("bt_connection:init(): self(): ~p", [self()]), + ?debug("bt_connection:init(): Channel: ~p", [Channel]), + ?debug("bt_connection:init(): ParentPid: ~p", [ParentPid]), + ?debug("bt_connection:init(): Module: ~p", [Mod]), + ?debug("bt_connection:init(): Function: ~p", [Fun]), + ?debug("bt_connection:init(): Arg: ~p", [Arg]), + + {ok, #st{ + channel = Channel, + parent_pid = ParentPid, + rfcomm_ref = ARef, + mod = Mod, + func = Fun, + args = Arg + }}. + + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling call messages +%% +%% @spec handle_call(Request, From, State) -> +%% {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_call(terminate_connection, _From, St) -> + ?debug("~p:handle_call(terminate_connection): Terminating: ~p", + [ ?MODULE, {St#st.remote_addr, St#st.channel}]), + + {stop, Reason, NSt} = handle_info({tcp_closed, St#st.rfcomm_ref}, St), + {stop, Reason, ok, NSt}; + +handle_call(_Request, _From, State) -> + ?warning("~p:handle_call(): Unknown call: ~p", [ ?MODULE, _Request]), + Reply = ok, + {reply, Reply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling cast messages +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast({send, Data}, St) -> + ?debug("~p:handle_call(send): Sending: ~p", + [ ?MODULE, Data]), + +%% gen_tcp:send(St#st.sock, term_to_binary(Data)), + + {noreply, St}; + +handle_cast(_Msg, State) -> + ?warning("~p:handle_cast(): Unknown call: ~p", [ ?MODULE, _Msg]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling all non call/cast messages +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- + +%% An accept reference we've setup now has accetpted an +%% incoming connection. +handle_info({ARef, {accept, BTAddr, Channel} }, + #st { rfcomm_ref = ConnRef, + parent_pid = PPid } = St) + when ConnRef =:= ARef -> + io:format("bt_connection from ~w:~w\n", [BTAddr,Channel]), + PPid ! {accept, ARef, Channel, ok}, + { noreply, St#st { remote_addr = BTAddr, + channel = Channel } }; + +handle_info({tcp, _ConnRef, Data}, + #st { remote_addr = BTAddr, + channel = Channel, + mod = Mod, + func = Fun, + args = Arg } = State) -> + ?debug("~p:handle_info(data): Data: ~p", [ ?MODULE, Data]), + ?debug("~p:handle_info(data): From: ~p:~p ", [ ?MODULE, BTAddr, Channel]), + + try binary_to_term(Data) of + Term -> + ?debug("~p:handle_info(data): Term: ~p", [ ?MODULE, Term]), + FromPid = self(), + spawn(fun() -> Mod:Fun(FromPid, BTAddr, Channel, + data, Term, Arg) end) + catch + _:_ -> + ?warning("~p:handle_info(data): Data could not be decoded: ~pp", + [ ?MODULE, Data]) + + end, +%% inet:setopts(Sock, [{active, once}]), + {noreply, State}; + + +handle_info({rfcomm_closed, ConnRef}, + #st { remote_addr = BTAddr, + channel = Channel, + mod = Mod, + func = Fun, + args = Arg } = State) -> + ?debug("~p:handle_info(tcp_closed): BTAddr: ~p:~p ", [ ?MODULE, BTAddr, Channel]), + Mod:Fun(self(), BTAddr, Channel, closed, Arg), + connection_manager:delete_connection_by_pid(self()), + rfcomm_close:close(ConnRef), + {stop, normal, State}; + + +handle_info({rfcomm_error, ConnRef}, + #st { remote_addr = BTAddr, + channel = Channel, + mod = Mod, + func = Fun, + args = Arg} = State) -> + + ?debug("~p:handle_info(tcp_error): BTAddr: ~p:~p ", [ ?MODULE, BTAddr, Channel]), + Mod:Fun(self(), BTAddr, Channel, error, Arg), + rfcomm:close(ConnRef), + connection_manager:delete_connection_by_pid(self()), + {stop, normal, State}; + + +handle_info(_Info, State) -> + ?warning("~p:handle_cast(): Unknown info: ~p", [ ?MODULE, _Info]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ?debug("~p:terminate(): Reason: ~p ", [ ?MODULE, _Reason]), + ok. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/components/dlink_bt/src/bt_connection_manager.erl b/components/dlink_bt/src/bt_connection_manager.erl new file mode 100644 index 0000000..de665a2 --- /dev/null +++ b/components/dlink_bt/src/bt_connection_manager.erl @@ -0,0 +1,266 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% +%% +%%%------------------------------------------------------------------- +%%% @author magnus <magnus@t520.home> +%%% @copyright (C) 2014, magnus +%%% @doc +%%% +%%% @end +%%% Created : 12 Sep 2014 by magnus <magnus@t520.home> +%%%------------------------------------------------------------------- +-module(bt_connection_manager). + +-behaviour(gen_server). +-include_lib("lager/include/log.hrl"). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([add_connection/3]). +-export([delete_connection_by_pid/1]). +-export([delete_connection_by_address/2]). +-export([find_connection_by_pid/1]). +-export([find_connection_by_address/2]). + +-define(SERVER, ?MODULE). + +-record(st, { + conn_by_pid = undefined, + conn_by_addr = undefined + }). + +%%%=================================================================== +%%% API +%%%=================================================================== + +add_connection(BTAddr, Channel, Pid) -> + gen_server:call(?SERVER, { add_connection, BTAddr, Channel, Pid}). + +delete_connection_by_pid(Pid) -> + gen_server:call(?SERVER, { delete_connection_by_pid, Pid } ). + +delete_connection_by_address(BTAddr, Channel) -> + gen_server:call(?SERVER, { delete_connection_by_address, BTAddr, Channel } ). + +find_connection_by_pid(Pid) -> + gen_server:call(?SERVER, { find_connection_by_pid, Pid } ). + +find_connection_by_address(BTAddr, Channel) -> + gen_server:call(?SERVER, { find_connection_by_address, BTAddr, Channel } ). + + +%%-------------------------------------------------------------------- +%% @doc +%% Starts the server +%% +%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @end +%%-------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initializes the server +%% +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @end +%%-------------------------------------------------------------------- +init([]) -> + {ok, #st{ + conn_by_pid = dict:new(), %% All managed connection stored by pid + conn_by_addr = dict:new() %% All managed connection stored by address + }}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling call messages +%% +%% @spec handle_call(Request, From, State) -> +%% {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_call({add_connection, BTAddr, Channel, Pid}, _From, + #st { conn_by_pid = ConPid, + conn_by_addr = ConBTAddr} = St) -> + + ?debug("~p:handle_call(add): Adding Pid: ~p, BTAddress: ~p", + [ ?MODULE, Pid, { BTAddr, Channel }]), + %% Store so that we can find connection both by pid and by address + NConPid = dict:store(Pid, { BTAddr, Channel }, ConPid), + NConBTAddr = dict:store({ BTAddr, Channel }, Pid, ConBTAddr), + + NSt = St#st { conn_by_pid = NConPid, + conn_by_addr = NConBTAddr }, + {reply, ok, NSt}; + +%% Delete connection by pid +handle_call({delete_connection_by_pid, Pid}, _From, + #st { conn_by_pid = ConPid, + conn_by_addr = ConBTAddr} = St) when is_pid(Pid)-> + + %% Find address associated with Pid + case dict:find(Pid, ConPid) of + error -> + ?debug("~p:handle_call(del_by_pid): not found: ~p", + [ ?MODULE, Pid]), + { reply, not_found, St}; + + {ok, BTAddr } -> + ?debug("~p:handle_call(del_by_pid): deleted Pid: ~p, BTAddress: ~p", + [ ?MODULE, Pid, BTAddr]), + + NConPid = dict:erase(Pid, ConPid), + NConBTAddr = dict:erase(BTAddr, ConBTAddr), + + NSt = St#st { conn_by_pid = NConPid, + conn_by_addr = NConBTAddr }, + + {reply, ok, NSt} + end; + + +%% Delete connection by address +handle_call({ delete_connection_by_address, BTAddr, Channel}, _From, + #st { conn_by_pid = ConPid, + conn_by_addr = ConBTAddr} = St) -> + + %% Find Pid associated with BTAddress + case dict:find({BTAddr, Channel}, ConBTAddr) of + error -> + ?debug("~p:handle_call(del_by_addr): not found: ~p", + [ ?MODULE, {BTAddr, Channel}]), + { reply, not_found, St}; + + {ok, Pid } -> + ?debug("~p:handle_call(del_by_addr): deleted Pid: ~p, BTAddress: ~p", + [ ?MODULE, Pid, {BTAddr, Channel}]), + NConPid = dict:erase(Pid, ConPid), + NConBTAddr = dict:erase({ BTAddr, Channel }, ConBTAddr), + NSt = St#st { conn_by_pid = NConPid, + conn_by_addr = NConBTAddr }, + {reply, ok, NSt} + end; + + +%% Find connection by pid +handle_call({ find_connection_by_pid, Pid}, _From, + #st { conn_by_pid = ConPid} = St) when is_pid(Pid)-> + + %% Find address associated with Pid + case dict:find(Pid, ConPid) of + error -> + ?debug("~p:handle_call(find_by_pid): not found: ~p", + [ ?MODULE, Pid]), + { reply, not_found, St}; + + {ok, {BTAddr, Channel} } -> + ?debug("~p:handle_call(find_by_addr): Pid: ~p ->: ~p", + [ ?MODULE, Pid, {BTAddr, Channel}]), + {reply, {ok, BTAddr, Channel}, St} + end; + +%% Find connection by address +handle_call({find_connection_by_address, BTAddr, Channel}, _From, + #st { conn_by_addr = ConBTAddr} = St) -> + + %% Find address associated with Pid + case dict:find({BTAddr, Channel}, ConBTAddr) of + error -> + ?debug("~p:handle_call(find_by_addr): not found: ~p", + [ ?MODULE, {BTAddr, Channel}]), + + { reply, not_found, St}; + + {ok, Pid } -> + ?debug("~p:handle_call(find_by_addr): BTAddr: ~p ->: ~p", + [ ?MODULE, {BTAddr, Channel}, Pid]), + {reply, {ok, Pid}, St} + end; + + +handle_call(_Request, _From, State) -> + ?warning("~p:handle_call(): Unknown call: ~p", [ ?MODULE, _Request]), + Reply = ok, + {reply, Reply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling cast messages +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast(_Msg, State) -> + ?warning("~p:handle_cast(): Unknown call: ~p", [ ?MODULE, _Msg]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling all non call/cast messages +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> + ?warning("~p:handle_cast(): Unknown info: ~p", [ ?MODULE, _Info]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/components/dlink_bt/src/bt_gen_nb_server.erl b/components/dlink_bt/src/bt_gen_nb_server.erl new file mode 100644 index 0000000..4a9a630 --- /dev/null +++ b/components/dlink_bt/src/bt_gen_nb_server.erl @@ -0,0 +1,205 @@ +%% Copyright (c) 2009 Hypothetical Labs, Inc. + +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. + +-module(bt_gen_nb_server). + +-author('kevin@hypotheticalabs.com'). + +-behaviour(gen_server). + +%% API +-export([start_link/2, + start_link/3, + get_cb_state/1, + store_cb_state/2, + add_listen_socket/2, + remove_listen_socket/2]). + +%% Behavior callbacks +-export([behaviour_info/1]). + +%% gen_server callbacks +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, {cb, + addrs=dict:new(), + socks=dict:new(), + server_state}). + +%% @hidden +behaviour_info(callbacks) -> + [{init, 2}, + {handle_call, 3}, + {handle_cast, 2}, + {handle_info, 2}, + {terminate, 2}, + {sock_opts, 0}, + {new_connection, 4}]; + +behaviour_info(_) -> + undefined. + +%% @doc Start server listening on BTAddr:Channel +-spec start_link(atom(), [any()]) -> ok | ignore | {error, any()}. +start_link(CallbackModule, InitParams) -> + gen_server:start_link(?MODULE, [CallbackModule, InitParams], [{fullsweep_after, 0}]). + + +%% @doc Start server listening on BTAddr:Channel registered as Name +-spec start_link(atom(), atom(), [any()]) -> ok | ignore | {error, any()}. +start_link(Name, CallbackModule, InitParams) -> + gen_server:start_link(Name, ?MODULE, [CallbackModule, InitParams], [{fullsweep_after, 0}]). + +%% @doc Extracts the callback module's state from the server's overall state +%% NOTE: Should only be called by the submodule +-spec get_cb_state(#state{}) -> any(). +get_cb_state(#state{server_state=SState}) -> + SState. + +%% @doc Stores the callback module's state into the server's state +%% NOTE: Should only be called by the submodule +-spec store_cb_state(any(), #state{}) -> #state{}. +store_cb_state(CBState, State) when is_record(State, state) -> + State#state{server_state=CBState}. + +%% @doc Adds a new listener socket to be managed by gen_nb_server +%% NOTE: Should only be called by the submodule +-spec add_listen_socket({string(), integer()}, #state{}) -> {ok, #state{}} | {error, any()}. +add_listen_socket({BTAddr, Channel}, #state{cb=Callback, addrs=Addrs, socks=Socks}=State) -> + Key = {BTAddr, Channel}, + case dict:find(Key, Socks) of + {ok, _} -> + {error, already_listening}; + error -> + case rfcomm:listen(Channel) of + {ok, Sock} -> + {ok, State#state{socks=dict:store(Key, Sock, Socks), + addrs=dict:store(Sock, Key, Addrs)}}; + Error -> + Error + end + end. + +%% @doc Removes a new listener socket to be managed by gen_nb_server +%% NOTE: Should only be called by the submodule +-spec remove_listen_socket({string(), integer()}, #state{}) -> {error, not_listening} | {ok, #state{}}. +remove_listen_socket({BTAddr, Channel}, #state{socks=Socks, addrs=Addrs}=State) -> + Key = {BTAddr, Channel}, + case dict:find(Key, Socks) of + error -> + {error, not_listening}; + {ok, Sock} -> + rfcomm:close(Sock), + {ok, State#state{socks=dict:erase(Key, Socks), + addrs=dict:erase(Sock, Addrs)}} + end. + +%% @doc Returns the callback module's state +-spec init([atom()|any()]) -> {ok, #state{}} | {error, bad_init_state} | {error, any()}. + +init([CallbackModule, InitParams]) -> + process_flag(trap_exit, true), + State = #state{cb=CallbackModule}, + case CallbackModule:init(InitParams, State) of + {ok, ServerState} when is_record(ServerState, state) -> + {ok, ServerState}; + {ok, _State} -> + {error, bad_init_state}; + Err -> + Err + end. + +%% @hidden +handle_call(Request, From, #state{cb=Callback}=State) -> + case Callback:handle_call(Request, From, State) of + {reply, Reply, NewServerState} -> + {reply, Reply, NewServerState}; + {reply, Reply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) -> + {reply, Reply, NewServerState, Arg}; + {noreply, NewServerState} -> + {noreply, NewServerState}; + {noreply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) -> + {noreply, NewServerState, Arg}; + {stop, Reason, NewServerState} -> + {stop, Reason, NewServerState}; + {stop, Reason, Reply, NewServerState} -> + {stop, Reason, Reply, NewServerState} + end. + +%% @hidden +handle_cast(Msg, #state{cb=Callback}=State) -> + case Callback:handle_cast(Msg, State) of + {noreply, NewServerState} -> + {noreply, NewServerState}; + {noreply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) -> + {noreply, NewServerState, Arg}; + {stop, Reason, NewServerState} -> + {stop, Reason, NewServerState} + end. + +%% @hidden +handle_info({inet_async, ListSock, _Ref, {ok, CliSocket}}, #state{cb=Callback, addrs=Addrs}=State) -> + inet_db:register_socket(CliSocket, inet_tcp), + {BTAddr, Channel} = dict:fetch(ListSock, Addrs), + case Callback:new_connection(BTAddr, Channel, CliSocket, State) of + {ok, NewServerState} -> + prim_inet:async_accept(ListSock, -1), + {noreply, NewServerState}; + {stop, Reason, NewServerState} -> + {stop, Reason, NewServerState} + end; + +handle_info(Info, #state{cb=Callback}=State) -> + case Callback:handle_info(Info, State) of + {noreply, NewServerState} -> + {noreply, NewServerState}; + {noreply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) -> + {noreply, NewServerState, Arg}; + {stop, Reason, NewServerState} -> + {stop, Reason, NewServerState} + end. + +%% @hidden +terminate(Reason, #state{cb=Callback, addrs=Addrs}=State) -> + [gen_tcp:close(Sock) || Sock <- dict:fetch_keys(Addrs)], + State1 = State#state{addrs=dict:new(), socks=dict:new()}, + Callback:terminate(Reason, State1), + ok. + +%% @hidden +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%% @hidden +%% @spec convert(Addr) -> Result +%% Addr = string() +%% Result = {integer(), integer(), integer(), integer()} +%% @doc Converts text IP addresses "0.0.0.0" to tuples {0, 0, 0, 0} +convert(Addr) -> + T = string:tokens(Addr, "."), + list_to_tuple([list_to_integer(X) || X <- T]). diff --git a/components/dlink_bt/src/bt_listener.erl b/components/dlink_bt/src/bt_listener.erl new file mode 100644 index 0000000..2ec3c39 --- /dev/null +++ b/components/dlink_bt/src/bt_listener.erl @@ -0,0 +1,93 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% + +%% Setup a listen socket and manage connections to remote parties. +%% Can also retrieve connections by peer address. +-module(bt_listener). + +-include_lib("lager/include/log.hrl"). + +-export([start_link/0, + add_listener/1, + remove_listener/1]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). +-export([terminate/2]). + +-record(st, {listeners = [], + acceptors = []}). + + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +add_listener(Channel) -> + gen_server:call(?MODULE, {add_listener, Channel}). + +remove_listener(Channel) -> + gen_server:call(?MODULE, {remove_listener, Channel}). + +init([]) -> + {ok, #st { listeners = [] }}. + + +handle_call({add_listener, Channel}, _From, State) -> + ?info("bt_listener:add_listener(): Setting up listener on channel ~p", [ Channel]), + + case rfcomm:listen(Channel) of + {ok, ListenRef} -> + ?info("bt_listener:add_listener(): ListenRef: ~p", [ ListenRef]), + { noreply, NSt} = handle_info({accept, ListenRef, Channel, ok}, State), + + { reply, + ok, + NSt#st { + listeners = [ { ListenRef, Channel } | NSt#st.listeners ] + } + }; + + Err -> + ?info("bt_listener:add_listener(): Failed: ~p", [ Err]), + {reply, Err, State} + + end; + +handle_call({remove_listener, _Channel}, _From, State) -> + ?warning("FIXME: bt_listener:remove_listener()"), + {reply, ok, State}; + +handle_call(_Msg, _From, State) -> + {reply, ignored, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({accept, ListenRef, Channel, ok} , St) -> + %% Fire up a new process to handle the + %% future incoming connection. + ?info("bt_listener:accept(): ListenRef: ~p", [ ListenRef]), + + {ok, ConnPid} = bt_connection:accept(Channel, + ListenRef, + self(), + dlink_bt_rpc, + handle_socket, + nil), + + + {noreply, St#st {acceptors = [ { Channel, ConnPid } | St#st.acceptors ]}}; + +handle_info(_Msg, State) -> + + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + + + diff --git a/components/dlink_bt/src/dlink_bt.app.src b/components/dlink_bt/src/dlink_bt.app.src new file mode 100644 index 0000000..2a09cf5 --- /dev/null +++ b/components/dlink_bt/src/dlink_bt.app.src @@ -0,0 +1,23 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% + + +%% -*- erlang -*- +{application, dlink_bt, + [ + {description, ""}, + {vsn, "0.1"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + rvi_common + ]}, + {mod, { dlink_bt_app, []}}, + {start_phases, [{json_rpc, []}, {connection_manager, []}]} + ]}. diff --git a/components/dlink_bt/src/dlink_bt_app.erl b/components/dlink_bt/src/dlink_bt_app.erl new file mode 100644 index 0000000..2877c5d --- /dev/null +++ b/components/dlink_bt/src/dlink_bt_app.erl @@ -0,0 +1,39 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% + + +-module(dlink_bt_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, + start_phase/3, + stop/1]). + +%% =================================================================== +%% Application callbacks +%% =================================================================== + +start(_StartType, _StartArgs) -> + dlink_bt_sup:start_link(). + +start_phase(init, _, _) -> + dlink_bt_rpc:init_rvi_component(); + +start_phase(json_rpc, _, _) -> + dlink_bt_rpc:start_json_server(), + ok; + +start_phase(connection_manager, _, _) -> + dlink_bt_rpc:start_connection_manager(), + ok. + + +stop(_State) -> + ok. diff --git a/components/dlink_bt/src/dlink_bt_rpc.erl b/components/dlink_bt/src/dlink_bt_rpc.erl new file mode 100644 index 0000000..ddcd34b --- /dev/null +++ b/components/dlink_bt/src/dlink_bt_rpc.erl @@ -0,0 +1,680 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% + + +-module(dlink_bt_rpc). +-behavior(gen_server). + +-export([handle_rpc/2]). +-export([handle_notification/2]). +-export([handle_socket/6]). +-export([handle_socket/5]). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([start_json_server/0]). +-export([start_connection_manager/0]). + +%% Invoked by service discovery +%% FIXME: Should be rvi_service_discovery behavior +-export([service_available/3, + service_unavailable/3]). + +-export([setup_data_link/3, + disconnect_data_link/2, + send_data/5]). + + +-include_lib("lager/include/log.hrl"). +-include_lib("rvi_common/include/rvi_common.hrl"). + +-define(DEFAULT_BT_CHANNEL, 1). +-define(DEFAULT_RECONNECT_INTERVAL, 1000). +-define(DEFAULT_PING_INTERVAL, 300000). %% Five minutes +-define(SERVER, ?MODULE). + +-define(CONNECTION_TABLE, rvi_dlink_bt_connections). +-define(SERVICE_TABLE, rvi_dlink_bt_services). + +%% Multiple registrations of the same service, each with a different connection, +%% is possible. +-record(service_entry, { + service = [], %% Name of service + connections = undefined %% PID of connection that can reach this service + }). + +-record(connection_entry, { + connection = undefined, %% PID of connection that has a set of services. + services = [] %% List of service names available through this connection + }). + +-record(st, { + cs = #component_spec{} + }). + + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + ?info("dlink_bt:init(): Called"), + %% Dig out the bert rpc server setup + + ets:new(?SERVICE_TABLE, [ set, public, named_table, + { keypos, #service_entry.service }]), + + ets:new(?CONNECTION_TABLE, [ set, public, named_table, + { keypos, #connection_entry.connection }]), + + CS = rvi_common:get_component_specification(), + service_discovery_rpc:subscribe(CS, ?MODULE), + + {ok, #st { + cs = CS + } + }. + +start_json_server() -> + rvi_common:start_json_rpc_server(data_link, ?MODULE, dlink_bt_sup). + + +start_connection_manager() -> + CompSpec = rvi_common:get_component_specification(), + {ok, BertOpts } = rvi_common:get_module_config(data_link, + ?MODULE, + server_opts, + [], + CompSpec), + %% Retrieve the channel we should use + Channel = proplists:get_value(channel, BertOpts, ?DEFAULT_BT_CHANNEL), + + ?info("dlink_bt:init_rvi_component(~p): Starting listener.", [self()]), + + %% Fire up listener + + bt:start(), + bt:debug(debug), + bt_listener:start_link(), + bt_connection_manager:start_link(), + ?info("dlink_bt:start_connection_manager(): Adding listener on bluetooth channel ~p", [Channel ]), + + %% Add listener channel. + case bt_listener:add_listener(Channel) of + ok -> + ok; + + Err -> + ?error("dlink_bt:init_rvi_component(): Failed to launch listener: ~p", [ Err ]), + ok + end, + + ok. + +service_available(CompSpec, SvcName, DataLinkModule) -> + rvi_common:notification(data_link, ?MODULE, + service_available, + [{ service, SvcName }, + { data_link_module, DataLinkModule }], + CompSpec). + +service_unavailable(CompSpec, SvcName, DataLinkModule) -> + rvi_common:notification(data_link, ?MODULE, + service_unavailable, + [{ service, SvcName }, + { data_link_module, DataLinkModule }], + CompSpec). + + +setup_data_link(CompSpec, Service, Opts) -> + rvi_common:request(data_link, ?MODULE, setup_data_link, + [ { service, Service }, + { opts, Opts }], + [status, timeout], CompSpec). + +disconnect_data_link(CompSpec, NetworkAddress) -> + rvi_common:request(data_link, ?MODULE, disconnect_data_link, + [ {network_address, NetworkAddress} ], + [status], CompSpec). + + +send_data(CompSpec, ProtoMod, Service, DataLinkOpts, Data) -> + rvi_common:request(data_link, ?MODULE, send_data, + [ { proto_mod, ProtoMod }, + { service, Service }, + { data, Data }, + { opts, DataLinkOpts } + ], + [status], CompSpec). + + +%% End of behavior + +%% +%% Connect to a remote RVI node. +%% +connect_remote(BTAddr, Channel, CompSpec) -> + case bt_connection_manager:find_connection_by_address(BTAddr, Channel) of + { ok, _Pid } -> + already_connected; + + not_found -> + %% Setup a new outbound connection + ?info("dlink_bt:connect_remote(): Connecting ~p:~p", + [BTAddr, Channel]), + + %%FIXME + case rfcomm:open(BTAddr, Channel) of + { ok, Ref } -> + ?info("dlink_bt:connect_remote(): Connected ~p:~p", + [BTAddr, Channel]), + + %% Setup a genserver around the new connection. + {ok, Pid } = connection:setup(BTAddr, Channel, Ref, + ?MODULE, handle_socket, CompSpec ), + + %% Send authorize + { LocalBTAddr, LocalChannel} = rvi_common:node_address_tuple(), + connection:send(Pid, + { authorize, + 1, LocalBTAddr, LocalChannel, rvi_binary, + { certificate, {}}, { signature, {}} }), + ok; + + {error, Err } -> + ?info("dlink_bt:connect_remote(): Failed ~p:~p: ~p", + [BTAddr, Channel, Err]), + not_available + end + end. + + +connect_and_retry_remote( BTAddr, Channel, CompSpec) -> + ?info("dlink_bt:connect_and_retry_remote(): ~p:~p", + [ BTAddr, Channel]), + + case connect_remote(BTAddr, list_to_integer(Channel), CompSpec) of + ok -> ok; + + Err -> %% Failed to connect. Sleep and try again + ?notice("dlink_bt:connect_and_retry_remote(~p:~p): Failed: ~p", + [BTAddr, Channel, Err]), + + ?notice("dlink_bt:connect_and_retry_remote(~p:~p): Will try again in ~p sec", + [BTAddr, Channel, ?DEFAULT_RECONNECT_INTERVAL]), + + setup_reconnect_timer(?DEFAULT_RECONNECT_INTERVAL, BTAddr, Channel, CompSpec), + + not_available + end. + + +announce_local_service_(_CompSpec, [], _Service, _Availability) -> + ok; + +announce_local_service_(CompSpec, + [ConnPid | T], + Service, Availability) -> + + Res = connection:send(ConnPid, + {service_announce, 3, Availability, + [Service], { signature, {}}}), + + ?debug("dlink_bt:announce_local_service(~p: ~p) -> ~p Res: ~p", + [ Availability, Service, ConnPid, Res]), + + %% Move on to next connection. + announce_local_service_(CompSpec, + T, + Service, Availability). + +announce_local_service_(CompSpec, Service, Availability) -> + announce_local_service_(CompSpec, + get_connections(), + Service, Availability). + + +handle_socket(_FromPid, PeerBTAddr, PeerChannel, data, ping, [_CompSpec]) -> + ?info("dlink_bt:ping(): Pinged from: ~p:~p", [ PeerBTAddr, PeerChannel]), + ok; + +handle_socket(FromPid, PeerBTAddr, PeerChannel, data, + { authorize, + TransactionID, + RemoteAddress, + RemoteChannel, + Protocol, + Certificate, + Signature}, [CompSpec]) -> + + ?info("dlink_bt:authorize(): Peer Address: ~p:~p", [PeerBTAddr, PeerChannel ]), + ?info("dlink_bt:authorize(): Remote Address: ~p~p", [ RemoteAddress, RemoteChannel ]), + ?info("dlink_bt:authorize(): Protocol: ~p", [ Protocol ]), + ?debug("dlink_bt:authorize(): TransactionID: ~p", [ TransactionID ]), + ?debug("dlink_bt:authorize(): Certificate: ~p", [ Certificate ]), + ?debug("dlink_bt:authorize(): Signature: ~p", [ Signature ]), + + + { LocalAddress, LocalChannel } = rvi_common:node_address_tuple(), + + %% If FromPid (the genserver managing the socket) is not yet registered + %% with the conneciton manager, this is an incoming connection + %% from the client. We should respond with our own authorize followed by + %% a service announce + + %% FIXME: Validate certificate and signature before continuing. + case connection_manager:find_connection_by_pid(FromPid) of + not_found -> + ?info("dlink_bt:authorize(): New connection!"), + connection_manager:add_connection(RemoteAddress, RemoteChannel, FromPid), + ?debug("dlink_bt:authorize(): Sending authorize."), + Res = connection:send(FromPid, + { authorize, + 1, LocalAddress, LocalChannel, rvi_binary, + {certificate, {}}, { signature, {}}}), + ?debug("dlink_bt:authorize(): Sending authorize: ~p", [ Res]), + ok; + _ -> ok + end, + + %% Send our own servide announcement to the remote server + %% that just authorized to us. + [ ok, LocalServices ] = service_discovery_rpc:get_services_by_module(CompSpec, local), + + + %% Send an authorize back to the remote node + ?info("dlink_bt:authorize(): Announcing local services: ~p to remote ~p:~p", + [LocalServices, RemoteAddress, RemoteChannel]), + + connection:send(FromPid, + { service_announce, 2, available, + LocalServices, { signature, {}}}), + + %% Setup ping interval + gen_server:call(?SERVER, { setup_initial_ping, RemoteAddress, RemoteChannel, FromPid }), + ok; + +handle_socket(FromPid, RemoteBTAddr, RemoteChannel, data, + { service_announce, + TransactionID, + available, + Services, + Signature }, [CompSpec]) -> + ?debug("dlink_bt:service_announce(available): Address: ~p:~p", [ RemoteBTAddr, RemoteChannel ]), + ?debug("dlink_bt:service_announce(available): Remote Channel: ~p", [ RemoteChannel ]), + ?debug("dlink_bt:service_announce(available): TransactionID: ~p", [ TransactionID ]), + ?debug("dlink_bt:service_announce(available): Signature: ~p", [ Signature ]), + ?debug("dlink_bt:service_announce(available): Service: ~p", [ Services ]), + + + add_services(Services, FromPid), + + service_discovery_rpc:register_services(CompSpec, Services, ?MODULE), + ok; + + +handle_socket(FromPid, RemoteBTAddr, RemoteChannel, data, + { service_announce, + TransactionID, + unavailable, + Services, + Signature}, [CompSpec]) -> + ?debug("dlink_bt:service_announce(unavailable): Address: ~p:~p", [ RemoteBTAddr, RemoteChannel ]), + ?debug("dlink_bt:service_announce(unavailable): Remote Channel: ~p", [ RemoteChannel ]), + ?debug("dlink_bt:service_announce(unavailable): TransactionID: ~p", [ TransactionID ]), + ?debug("dlink_bt:service_announce(unavailable): Signature: ~p", [ Signature ]), + ?debug("dlink_bt:service_announce(unavailable): Service: ~p", [ Services ]), + + %% Register the received services with all relevant components + + + %% Delete from our own tables. + + delete_services(FromPid, Services), + service_discovery_rpc:unregister_services(CompSpec, Services, ?MODULE), + ok; + + +handle_socket(_FromPid, SetupBTAddr, SetupChannel, data, + { receive_data, ProtocolMod, Data}, [CompSpec]) -> +%% ?info("dlink_bt:receive_data(): ~p", [ Data ]), + ?debug("dlink_bt:receive_data(): SetupAddress: {~p, ~p}", [ SetupBTAddr, SetupChannel ]), + ProtocolMod:receive_message(CompSpec, Data), + ok; + + +handle_socket(_FromPid, SetupBTAddr, SetupChannel, data, Data, [_CompSpec]) -> + ?warning("dlink_bt:unknown_data(): SetupAddress: {~p, ~p}", [ SetupBTAddr, SetupChannel ]), + ?warning("dlink_bt:unknown_data(): Unknown data: ~p", [ Data]), + ok. + + +%% We lost the socket connection. +%% Unregister all services that were routed to the remote end that just died. +handle_socket(FromPid, SetupBTAddr, SetupChannel, closed, [CompSpec]) -> + ?info("dlink_bt:closed(): SetupAddress: {~p, ~p}", [ SetupBTAddr, SetupChannel ]), + + NetworkAddress = SetupBTAddr ++ "-" ++ integer_to_list(SetupChannel), + + %% Get all service records associated with the given connection + LostSvcNameList = get_services_by_connection(FromPid), + + delete_connection(FromPid), + + %% Check if this was our last connection supchanneling each given service. + lists:map( + fun(SvcName) -> + case get_connections_by_service(SvcName) of + [] -> + service_discovery_rpc: + unregister_services(CompSpec, + [SvcName], + ?MODULE); + _ -> ok + end + end, LostSvcNameList), + + {ok, PersistentConnections } = rvi_common:get_module_config(data_link, + ?MODULE, + persistent_connections, + [], + CompSpec), + %% Check if this is a static node. If so, setup a timer for a reconnect + case lists:member(NetworkAddress, PersistentConnections) of + true -> + ?info("dlink_bt:closed(): Reconnect address: ~p", [ NetworkAddress ]), + ?info("dlink_bt:closed(): Reconnect interval: ~p", [ ?DEFAULT_RECONNECT_INTERVAL ]), + [ BTAddr, Channel] = string:tokens(NetworkAddress, "-"), + + setup_reconnect_timer(?DEFAULT_RECONNECT_INTERVAL, + BTAddr, Channel, CompSpec); + false -> ok + end, + ok; + +handle_socket(_FromPid, SetupBTAddr, SetupChannel, error, _ExtraArgs) -> + ?info("dlink_bt:socket_error(): SetupAddress: {~p, ~p}", [ SetupBTAddr, SetupChannel ]), + ok. + + +%% JSON-RPC entry point +%% CAlled by local exo http server +handle_notification("service_available", Args) -> + {ok, SvcName} = rvi_common:get_json_element(["service"], Args), + {ok, DataLinkModule} = rvi_common:get_json_element(["data_link_module"], Args), + + gen_server:cast(?SERVER, { rvi, service_available, + [ SvcName, + DataLinkModule ]}), + + ok; +handle_notification("service_unavailable", Args) -> + {ok, SvcName} = rvi_common:get_json_element(["service"], Args), + {ok, DataLinkModule} = rvi_common:get_json_element(["data_link_module"], Args), + + gen_server:cast(?SERVER, { rvi, service_unavailable, + [ SvcName, + DataLinkModule ]}), + + ok; + +handle_notification(Other, _Args) -> + ?info("dlink_bt:handle_notification(~p): unknown", [ Other ]), + ok. + +handle_rpc("setup_data_link", Args) -> + { ok, Service } = rvi_common:get_json_element(["service"], Args), + + { ok, Opts } = rvi_common:get_json_element(["opts"], Args), + + [ Res, Timeout ] = gen_server:call(?SERVER, { rvi, setup_data_link, + [ Service, Opts ] }), + + {ok, [ {status, rvi_common:json_rpc_status(Res)} , { timeout, Timeout }]}; + +handle_rpc("disconenct_data_link", Args) -> + { ok, NetworkAddress} = rvi_common:get_json_element(["network_address"], Args), + [Res] = gen_server:call(?SERVER, { rvi, disconnect_data_link, [NetworkAddress]}), + {ok, [ {status, rvi_common:json_rpc_status(Res)} ]}; + +handle_rpc("send_data", Args) -> + { ok, ProtoMod } = rvi_common:get_json_element(["proto_mod"], Args), + { ok, Service } = rvi_common:get_json_element(["service"], Args), + { ok, Data } = rvi_common:get_json_element(["data"], Args), + { ok, DataLinkOpts } = rvi_common:get_json_element(["opts"], Args), + [ Res ] = gen_server:call(?SERVER, { rvi, send_data, [ProtoMod, Service, Data, DataLinkOpts]}), + {ok, [ {status, rvi_common:json_rpc_status(Res)} ]}; + + +handle_rpc(Other, _Args) -> + ?info("dlink_bt:handle_rpc(~p): unknown", [ Other ]), + { ok, [ { status, rvi_common:json_rpc_status(invalid_command)} ] }. + + +handle_cast( {rvi, service_available, [SvcName, local]}, St) -> + ?debug("dlink_bt:service_available(): ~p (local)", [ SvcName ]), + announce_local_service_(St#st.cs, SvcName, available), + {noreply, St}; + + +handle_cast( {rvi, service_available, [SvcName, Mod]}, St) -> + ?debug("dlink_bt:service_available(): ~p (~p) ignored", [ SvcName, Mod ]), + %% We don't care about remote services available through + %% other data link modules + {noreply, St}; + + +handle_cast( {rvi, service_unavailable, [SvcName, local]}, St) -> + announce_local_service_(St#st.cs, SvcName, unavailable), + {noreply, St}; + +handle_cast( {rvi, service_unavailable, [_SvcName, _]}, St) -> + %% We don't care about remote services available through + %% other data link modules + {noreply, St}; + + +handle_cast(Other, St) -> + ?warning("dlink_bt:handle_cast(~p): unknown", [ Other ]), + {noreply, St}. + + +handle_call({rvi, setup_data_link, [ Service, Opts ]}, _From, St) -> + %% Do we already have a connection that supchannel service? + case get_connections_by_service(Service) of + [] -> %% Nope + case proplists:get_value(target, Opts, undefined) of + undefined -> + ?info("dlink_bt:setup_data_link(~p) Failed: no target given in options.", + [Service]), + { reply, [ok, -1 ], St }; + + Addr -> + [ Address, Channel] = string:tokens(Addr, "-"), + + case connect_remote(Address, list_to_integer(Channel), St#st.cs) of + ok -> + { reply, [ok, 2000], St }; %% 2 second timeout + + already_connected -> %% We are already connected + { reply, [already_connected, -1], St }; + + Err -> + { reply, [Err, 0], St } + end + end; + + _ -> %% Yes - We do have a connection that knows of service + { reply, [already_connected, -1], St } + end; + + +handle_call({rvi, disconnect_data_link, [NetworkAddress] }, _From, St) -> + [ Address, Channel] = string:tokens(NetworkAddress, "-"), + Res = connection:terminate_connection(Address,Channel), + { reply, [ Res ], St }; + + +handle_call({rvi, send_data, [ProtoMod, Service, Data, _DataLinkOpts]}, _From, St) -> + + %% Resolve connection pid from service + case get_connections_by_service(Service) of + [] -> + { reply, [ no_route ], St}; + + %% FIXME: What to do if we have multiple connections to the same service? + [ConnPid | _T] -> + Res = connection:send(ConnPid, {receive_data, ProtoMod, Data}), + { reply, [ Res ], St} + end; + + + + +handle_call({setup_initial_ping, Address, Channel, Pid}, _From, St) -> + %% Create a timer to handle periodic pings. + {ok, ServerOpts } = rvi_common:get_module_config(data_link, + ?MODULE, + server_opts, [], + St#st.cs), + Timeout = proplists:get_value(ping_interval, ServerOpts, ?DEFAULT_PING_INTERVAL), + + ?info("dlink_bt:setup_ping(): ~p:~p will be pinged every ~p msec", + [ Address, Channel, Timeout] ), + + erlang:send_after(Timeout, self(), { rvi_ping, Pid, Address, Channel, Timeout }), + + {reply, ok, St}; + +handle_call(Other, _From, St) -> + ?warning("dlink_bt:handle_rpc(~p): unknown", [ Other ]), + { reply, { ok, [ { status, rvi_common:json_rpc_status(invalid_command)} ]}, St}. + + + +%% Ping time +handle_info({ rvi_ping, Pid, Address, Channel, Timeout}, St) -> + + %% Check that connection is up + case connection:is_connection_up(Pid) of + true -> + ?info("dlink_bt:ping(): Pinging: ~p:~p", [Address, Channel]), + connection:send(Pid, ping), + erlang:send_after(Timeout, self(), + { rvi_ping, Pid, Address, Channel, Timeout }); + + false -> + ok + end, + {noreply, St}; + +%% Setup static nodes +handle_info({ rvi_setup_persistent_connection, BTAddr, Channel, CompSpec }, St) -> + connect_and_retry_remote(BTAddr, Channel, CompSpec), + { noreply, St }; + +handle_info(Info, St) -> + ?notice("dlink_bt(): Unkown message: ~p", [ Info]), + {noreply, St}. + +terminate(_Reason, _St) -> + ok. +code_change(_OldVsn, St, _Extra) -> + {ok, St}. + +setup_reconnect_timer(MSec, BTAddr, Channel, CompSpec) -> + erlang:send_after(MSec, ?MODULE, + { rvi_setup_persistent_connection, + BTAddr, Channel, CompSpec }), + ok. + + +get_services_by_connection(ConnPid) -> + case ets:lookup(?CONNECTION_TABLE, ConnPid) of + [ #connection_entry { services = SvcNames } ] -> + SvcNames; + [] -> [] + end. + + +get_connections_by_service(Service) -> + case ets:lookup(?SERVICE_TABLE, Service) of + [ #service_entry { connections = Connections } ] -> + Connections; + [] -> [] + end. + + +add_services(SvcNameList, ConnPid) -> + %% Create or replace existing connection table entry + %% with the sum of new and old services. + ets:insert(?CONNECTION_TABLE, + #connection_entry { + connection = ConnPid, + services = SvcNameList ++ get_services_by_connection(ConnPid) + }), + + %% Add the connection to the service entry for each servic. + [ ets:insert(?SERVICE_TABLE, + #service_entry { + service = SvcName, + connections = [ConnPid | get_connections_by_service(SvcName)] + }) || SvcName <- SvcNameList ], + ok. + + +delete_services(ConnPid, SvcNameList) -> + ets:insert(?CONNECTION_TABLE, + #connection_entry { + connection = ConnPid, + services = get_services_by_connection(ConnPid) -- SvcNameList + }), + + %% Loop through all services and update the conn table + %% Update them with a new version where ConnPid has been removed + [ ets:insert(?SERVICE_TABLE, + #service_entry { + service = SvcName, + connections = get_connections_by_service(SvcName) -- [ConnPid] + }) || SvcName <- SvcNameList ], + ok. + + + +delete_connection(Conn) -> + %% Create or replace existing connection table entry + %% with the sum of new and old services. + SvcNameList = get_services_by_connection(Conn), + + %% Replace each existing connection entry that has + %% SvcName with a new one where the SvcName is removed. + lists:map(fun(SvcName) -> + Existing = get_connections_by_service(SvcName), + ets:insert(?SERVICE_TABLE, # + service_entry { + service = SvcName, + connections = Existing -- [ Conn ] + }) + end, SvcNameList), + + %% Delete the connection + ets:delete(?CONNECTION_TABLE, Conn), + ok. + + + +get_connections('$end_of_table', Acc) -> + Acc; + +get_connections(Key, Acc) -> + get_connections(ets:next(?CONNECTION_TABLE, Key), [ Key | Acc ]). + + +get_connections() -> + get_connections(ets:first(?CONNECTION_TABLE), []). diff --git a/components/dlink_bt/src/dlink_bt_sup.erl b/components/dlink_bt/src/dlink_bt_sup.erl new file mode 100644 index 0000000..369f1a3 --- /dev/null +++ b/components/dlink_bt/src/dlink_bt_sup.erl @@ -0,0 +1,39 @@ +%% +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +%% + + +-module(dlink_bt_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +%% Helper macro for declaring children of supervisor +-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). + +%% =================================================================== +%% API functions +%% =================================================================== + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% =================================================================== +%% Supervisor callbacks +%% =================================================================== + +init([]) -> + {ok, { {one_for_one, 5, 10}, + [ + ?CHILD(dlink_bt_rpc, worker) + ]} }. + diff --git a/components/dlink_tcp/src/dlink_tcp_rpc.erl b/components/dlink_tcp/src/dlink_tcp_rpc.erl index 16ed4ed..78b0fdd 100644 --- a/components/dlink_tcp/src/dlink_tcp_rpc.erl +++ b/components/dlink_tcp/src/dlink_tcp_rpc.erl @@ -91,7 +91,7 @@ start_connection_manager() -> CompSpec = rvi_common:get_component_specification(), {ok, BertOpts } = rvi_common:get_module_config(data_link, ?MODULE, - bert_rpc_server, + server_opts, [], CompSpec), IP = proplists:get_value(ip, BertOpts, ?DEFAULT_BERT_RPC_ADDRESS), @@ -575,7 +575,7 @@ handle_call({setup_initial_ping, Address, Port, Pid}, _From, St) -> %% Create a timer to handle periodic pings. {ok, ServerOpts } = rvi_common:get_module_config(data_link, ?MODULE, - bert_rpc_server, [], + server_opts, [], St#st.cs), Timeout = proplists:get_value(ping_interval, ServerOpts, ?DEFAULT_PING_INTERVAL), diff --git a/components/rvi_common/include/rvi_common.hrl b/components/rvi_common/include/rvi_common.hrl index 223f7e5..ca62c25 100644 --- a/components/rvi_common/include/rvi_common.hrl +++ b/components/rvi_common/include/rvi_common.hrl @@ -25,5 +25,5 @@ -define(COMP_SPEC_SCHEDULE_DEFAULT, [ { schedule_rpc, gen_server, [] } ]). -define(COMP_SPEC_SERVICE_DISCOVERY_DEFAULT, [ { service_discovery_rpc, gen_server, [] } ]). -define(COMP_SPEC_AUTHORIZE_DEFAULT, [ { authorize_rpc, gen_server, [] }]). --define(COMP_SPEC_DATA_LINK_DEFAULT, [ { data_link_bert_rpc, gen_server, [] } ]). +-define(COMP_SPEC_DATA_LINK_DEFAULT, [ { dlink_tcp_rpc, gen_server, [] } ]). -define(COMP_SPEC_PROTOCOL_DEFAULT, [ { protocol, gen_server, [] } ]). diff --git a/rebar.config b/rebar.config index faf50d2..dbdddc9 100644 --- a/rebar.config +++ b/rebar.config @@ -8,6 +8,7 @@ {sub_dirs, ["rel", "components/rvi_common", "components/authorize", + "components/dlink_bt", "components/dlink_tcp", "components/proto_bert", "components/schedule", @@ -25,6 +26,7 @@ %% git@github.com:tonyrog/dthread.git and references %% given in gsms/rebar.config and uart/rebar.config, so %% we'll specify them with a working reference here instead. + {bt, ".*", {git, "git://github.com/magnusfeuer/bt.git", "HEAD"}}, {dthread, ".*", {git, "git://github.com/tonyrog/dthread.git", "HEAD"}}, {uart, ".*", {git, "git://github.com/tonyrog/uart.git", "HEAD"}}, {gsms, ".*", {git, "git://github.com/tonyrog/gsms.git", "HEAD"}}, diff --git a/rvi_backend.config b/rvi_backend.config index 480a3b8..ec0408e 100644 --- a/rvi_backend.config +++ b/rvi_backend.config @@ -136,7 +136,7 @@ %% from other rvi nodes. %% A specific NIC address can also be specified %% through the {ip, "192.168.0.1" } tuple. - { bert_rpc_server, [ { port, 8807 }]} + { server_opts, [ { port, 8807 }]} ] } ] diff --git a/rvi_sample.config b/rvi_sample.config index 8a678c2..2434a66 100644 --- a/rvi_sample.config +++ b/rvi_sample.config @@ -346,7 +346,7 @@ %% from other rvi nodes. %% A specific NIC address can also be specified %% through the {ip, "192.168.0.1" } tuple. - { bert_rpc_server, [ { port, 8807 }]}, + { server_opts, [ { port, 8807 }]}, { persistent_connections, [ "38.129.64.13:8807" ]} ] } diff --git a/src/rvi.app.src b/src/rvi.app.src index dabecef..6a257f8 100644 --- a/src/rvi.app.src +++ b/src/rvi.app.src @@ -23,6 +23,8 @@ service_edge, authorize, dlink_tcp, + bt, + dlink_bt, proto_bert ]}, {mod, { rvi_app, []}} diff --git a/tizen.config b/tizen.config index b0ffff8..2a673f8 100644 --- a/tizen.config +++ b/tizen.config @@ -327,7 +327,7 @@ %% from other rvi nodes. %% A specific NIC address can also be specified %% through the {ip, "192.168.0.1" } tuple. - { bert_rpc_server, [ { port, 8807 }]} + { server_opts, [ { port, 8807 }]} %% Setup persistent connections { persistent_connections, [ "38.129.64.13:8807" ]} |