summaryrefslogtreecommitdiff
path: root/deps/gsms
diff options
context:
space:
mode:
authorMagnus Feuer <mfeuer@jaguarlandrover.com>2015-04-14 08:26:38 -0700
committerMagnus Feuer <mfeuer@jaguarlandrover.com>2015-04-17 14:01:27 -0700
commitb6430f5418653a2c7680217ef692613dde6f600f (patch)
tree79264d2bfd8e2d08ef62b7dd5b226b51e9574b87 /deps/gsms
parent682c3666ca5297157ccd76183a2d586ac72ffb69 (diff)
downloadrvi_core-b6430f5418653a2c7680217ef692613dde6f600f.tar.gz
Initial commit of deps
Diffstat (limited to 'deps/gsms')
-rw-r--r--deps/gsms/LICENSE24
-rw-r--r--deps/gsms/README.md32
-rw-r--r--deps/gsms/include/gsms.hrl144
-rw-r--r--deps/gsms/rebar.config9
-rw-r--r--deps/gsms/src/ERRORCODES30
-rw-r--r--deps/gsms/src/README141
-rw-r--r--deps/gsms/src/gsms.app.src9
-rw-r--r--deps/gsms/src/gsms.erl110
-rw-r--r--deps/gsms/src/gsms_0338.erl370
-rw-r--r--deps/gsms/src/gsms_0705.erl972
-rw-r--r--deps/gsms/src/gsms_app.erl83
-rw-r--r--deps/gsms/src/gsms_codec.erl1067
-rw-r--r--deps/gsms/src/gsms_if_sup.erl78
-rw-r--r--deps/gsms/src/gsms_router.erl464
-rw-r--r--deps/gsms/src/gsms_sup.erl68
-rw-r--r--deps/gsms/src/gsms_uart.erl834
-rw-r--r--deps/gsms/src/log.hrl44
-rwxr-xr-xdeps/gsms/src/sim90080
-rwxr-xr-xdeps/gsms/src/sim900_ft1075k75
-rw-r--r--deps/gsms/sys.config47
-rw-r--r--deps/gsms/tetrapak/config.ini13
21 files changed, 4694 insertions, 0 deletions
diff --git a/deps/gsms/LICENSE b/deps/gsms/LICENSE
new file mode 100644
index 0000000..68ed063
--- /dev/null
+++ b/deps/gsms/LICENSE
@@ -0,0 +1,24 @@
+Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+
+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.
+
+Except as contained in this notice, the name(s) of the above copyright holders
+shall not be used in advertising or otherwise to promote the sale, use or other
+dealings in this Software without prior written authorization.
+
diff --git a/deps/gsms/README.md b/deps/gsms/README.md
new file mode 100644
index 0000000..9fb01ab
--- /dev/null
+++ b/deps/gsms/README.md
@@ -0,0 +1,32 @@
+
+# Start
+
+# Send SMS
+
+ gsms_router:send([{addr,"<number>"}], "Hello").
+
+# Receive SMS
+
+ {ok,Ref} = gsms_router:subscribe([]).
+ Pdu = receive {gsms,Ref,Pdu1} -> Pdu1 end.
+ Text = Pdu#gsms_deliver_pdu.ud.
+
+
+# Raspberry pi setup
+
+Setup internal serial port /dev/ttyAMA0 for use as
+modem or other serial com line then ttyAMA0 must be
+disable in /etc/inittab
+
+ # comment out in /etc/inittab
+ # T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
+
+Also remove rerences to ttyAMA0 in /boot/cmdline.txt
+If cmdline looks like
+
+ dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
+
+After change it should look like
+
+ dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
+
diff --git a/deps/gsms/include/gsms.hrl b/deps/gsms/include/gsms.hrl
new file mode 100644
index 0000000..72b0dea
--- /dev/null
+++ b/deps/gsms/include/gsms.hrl
@@ -0,0 +1,144 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% sms pdu format (ETSI 03.40)
+%%% @end
+%% ref: http://www.dreamfabric.com/sms/vp.html
+%%% Created : 15 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+
+-ifndef(__GSMS_HRL__).
+-define(__GSMS_HRL__, true).
+
+-record(gsms_addr,
+ {
+ type = unknown,
+ addr = ""
+ }).
+
+-type dcs_type() :: message|message_waiting|data.
+-type dcs_compression() :: compressed | uncompressed.
+-type dcs_alphabet() :: default|ucs2|octet.
+-type dcs_class() :: alert|me|sim|te.
+-type dcs_store() :: store | discard.
+-type dcs_active() :: active | inactive.
+-type dcs_wait_type() :: voicemail | fax | email | other.
+
+-record(gsms_dcs,
+ {
+ type = message :: dcs_type(),
+ compression = uncompressed :: dcs_compression(),
+ alphabet = default :: dcs_alphabet(),
+ class = alert :: dcs_class(),
+ store = store :: dcs_store(),
+ active = inactive :: dcs_active(), %% type = message_waiting
+ wait_type = other :: dcs_wait_type() %% type = message_waiting
+ }).
+
+-define(IE_CONCAT8, 16#00).
+-define(IE_PORT8, 16#04).
+-define(IE_PORT16, 16#05).
+-define(IE_CONCAT16, 16#08).
+
+-define(MAX_7BIT_LEN, 160). %% 153 with concat8 header
+-define(MAX_8BIT_LEN, 140). %% 134 with concat8 header
+-define(MAX_16BIT_LEN, 70). %% 67 with concat8 header
+
+
+-define(VP_RELATIVE, (60*60*24)). %% 1 day
+-define(DEFAULT_PID, 0).
+
+-define(MTI_SMS_DELIVER, 2#00).
+
+-type uint() :: non_neg_integer().
+-type uint8() :: 0..255.
+-type uint16() :: 0..65535.
+
+-type gsms_ie() ::
+ {concat,Ref::uint(),N::uint8(),I::uint8()} |
+ {concat16,Ref::uint16(),N::uint8(),I::uint8()} |
+ {concat8,Ref::uint8(),N::uint8(),I::uint8()} |
+ {port8, Dst::uint8(),Src::uint8()} |
+ {port16,Dst::uint16(),Src::uint16()}
+ .
+
+-record(gsms_deliver_pdu, {
+ smsc, %% :: #gsms_addr{} smsc information
+ rp=false, %% :1 reply path exists
+ udhi=false, %% :1 user data header exists
+ sri=false, %% :1 status report indication
+ res1=0, %% 0:1
+ res2=0, %% 0:1
+ mms=false, %% :1 more messages to send
+ addr, %% :: #gsms_addr{}
+ pid=?DEFAULT_PID, %% protocol identifier
+ dcs=#gsms_dcs{}, %% data coding scheme
+ scts, %% :7/binary
+ udh=[] :: [gsms_ie()], %% user data header
+ udl, %% length in septets/octets (depend on dcs)
+ ud
+ }).
+
+-define(MTI_SMS_SUBMIT, 2#01).
+
+-record(gsms_submit_pdu, {
+ smsc, %% ::gsms_addr{} smsc information
+ rp=false, %% :1 reply path exists
+ udhi=false, %% :1 user data header exists
+ srr=false, %% :1 status report request
+ vpf=relative, %% :2 validity periad format 0..3
+ rd=true, %% :1 reject duplicates
+ mref=0, %% :8
+ addr, %% ::gsms_addr{}
+ pid=?DEFAULT_PID, %% protocol identifier
+ dcs=#gsms_dcs{}, %% data coding scheme
+ vp=?VP_RELATIVE, %% vary depend on vpf
+ udh=[] :: [gsms_ie()], %% user data header
+ udl,
+ ud
+ }).
+
+-type gsms_addr() :: #gsms_addr{} | string().
+-type gsms_pid() :: uint8().
+-type gsms_port() :: uint16().
+-type gsms_vpf() :: none|relative|enhanced|absolute.
+
+-type gsms_pdu_option() ::
+ {smsc, gsms_addr()} |
+ {rp, boolean()} |
+ {udhi, boolean()} |
+ {udh, [gsms_ie()]} |
+ {srr, boolean()} |
+ {mref, uint8()} |
+ {vpf, gsms_vpf()} |
+ {vp, term()} | %% fixme
+ {addr, gsms_addr()} |
+ {pid, gsms_pid()} |
+ {type, dcs_type()} |
+ {class, dcs_class()} |
+ {alphabet, dcs_alphabet() } |
+ {compression, dcs_compression() } |
+ {store, dcs_store()} |
+ {active, dcs_active()} |
+ {wait_type, dcs_wait_type()} |
+ {dcs, #gsms_dcs{}}
+ .
+
+-type filter() ::
+ [filter()] |
+ {type, dcs_type()} |
+ {class, dcs_class()} |
+ {alphabet, dcs_alphabet()} |
+ {pid, gsms_pid()} |
+ {src, gsms_port()} |
+ {dst, gsms_port()} |
+ {anumber, gsms_addr()} |
+ {bnumber, gsms_addr()} |
+ {smsc, gsms_addr()} |
+ {reg_exp, re:mp() | iodata()} |
+ {'not', filter()} |
+ {'and', filter(), filter()} |
+ {'or', filter(), filter()}.
+
+-endif.
+
+
diff --git a/deps/gsms/rebar.config b/deps/gsms/rebar.config
new file mode 100644
index 0000000..5f6d3b4
--- /dev/null
+++ b/deps/gsms/rebar.config
@@ -0,0 +1,9 @@
+%% -*- erlang -*-
+%% Config file for gsms application
+{deps, [ {uart, ".*", {git, "git@github.com:tonyrog/uart.git"}},
+ {lager, ".*", {git, "git://github.com/Feuerlabs/lager.git"}}]}.
+
+
+%% {erl_opts, [debug_info, fail_on_warning, {d,debug}]}.
+{erl_opts, [debug_info, fail_on_warning]}.
+{sub_dirs, ["src"]}.
diff --git a/deps/gsms/src/ERRORCODES b/deps/gsms/src/ERRORCODES
new file mode 100644
index 0000000..0f8d184
--- /dev/null
+++ b/deps/gsms/src/ERRORCODES
@@ -0,0 +1,30 @@
+
+
++CMS ERROR
+Message service error
+Description
+These are the error codes for +CMS ERROR.
+
+Error code Meaning
+0-127 GSM 04.11 Annex E-2 values
+128-255 GSM 03.40 section 9.2.3.22 values
+300 Phone failure
+301 SMS service of phone reserved
+302 Operation not allowed
+303 Operation not supported
+304 Invalid PDU mode parameter
+305 Invalid text mode parameter
+310 SIM not inserted
+311 SIM PIN necessary
+312 PH-SIM PIN necessary
+313 SIM failure
+314 SIM busy
+315 SIM wrong
+320 Memory failure
+321 Invalid memory index
+322 Memory full
+330 SMSC (message service center) address unknown
+331 No network service
+332 Network timeout
+500 Unknown error
+512 Manufacturer specific
diff --git a/deps/gsms/src/README b/deps/gsms/src/README
new file mode 100644
index 0000000..7331707
--- /dev/null
+++ b/deps/gsms/src/README
@@ -0,0 +1,141 @@
+Turn off echo
+=============
+ ATE
+ |
+ | OK
+
+Check if pin code is needed
+===========================
+ AT+CPIN?
+ |
+ | +CPIN: READY
+ |
+ | OK
+
+Set pin code if not ready
+=========================
+ AT+CPIN="1234"
+ | ERROR
+
+Disable pin code when ready
+===========================
+ AT+CLCK="SC",0,"1234"
+ |
+
+Register in network
+===================
+ AT+CREG=1
+ | OK
+
+Check SMS capability
+====================
+ AT+CSMS=0
+ | +CSMS: 1,1,1
+ | ERROR
+
+Write message to storage
+========================
+ AT+CMGW="+46702575687"
+ > Hej <ctl-z>
+ | +CMGW: 1
+
+Send message from storage (1)
+=============================
+ AT+CMSS=1
+ | +CMS ERROR: 331 (network not available)
+ | +CMS ERROR: 303 (....)
+
+Service center
+==============
+ AT+CSCA?
+ | +CSCA: "+46705008999",145
+
+Text/Pdu mode
+=============
+ AT+CMGF=1 text mode
+ AT+CMGF=0 pdu mode
+
+Receive SMS notification
+========================
+ AT+CNMI=1,1 - New incoming SMS is displayed like
+
+ | +CMTI: "SM",<x> (x = counter)
+
+ AT+CNMI=1,2 - Display incoming messages on arrival (unsolicited)
+
+List SMS
+========
+ AT+CMGL
+ | +CMGL:<x>,"REC UNREAD","+number"
+ | message
+
+ AT+CMGL="ALL"
+
+ AT+CMGL=1 (pdu mode)
+ | +CMGL: <x>,<y>,<str>,<len>
+ | <PDU>
+ |
+
+Read SMS
+========
+ AT+CMGR=<x>
+
+Delete SMS
+==========
+ AT+CMGD=<x>(,<x>)*
+
+
+Send SMS in PDU mode
+====================
+
+
+ AT+CMGS=23 //Send message, 23 octets (excluding the two initial zeros)
+%% >0011000B916407281553F80000AA0AE8329BFD4697D9EC37<ctrl-z>
+%%)
+%% There are 23 octets in this message (46 'characters'). The first octet ("00")
+%% doesn't count, it is only an indicator of the length of the SMSC information
+%% supplied (0).
+%%
+%%
+
+Sigal quality
+
+AT+CSQ?
++CSQ:18,99
+
+AT+CSQ
+
++CSQ: 4,0
+
+OK
+
+Value RSSI dBm Condition
+2 -109 Marginal
+3 -107 Marginal
+4 -105 Marginal
+5 -103 Marginal
+6 -101 Marginal
+7 -99 Marginal
+8 -97 Marginal
+9 -95 Marginal
+10 -93 OK
+11 -91 OK
+12 -89 OK
+13 -87 OK
+14 -85 OK
+15 -83 Good
+16 -81 Good
+17 -79 Good
+18 -77 Good
+19 -75 Good
+20 -73 Excellent
+21 -71 Excellent
+22 -69 Excellent
+23 -67 Excellent
+24 -65 Excellent
+25 -63 Excellent
+26 -61 Excellent
+27 -59 Excellent
+28 -57 Excellent
+29 -55 Excellent
+30 -53 Excellent \ No newline at end of file
diff --git a/deps/gsms/src/gsms.app.src b/deps/gsms/src/gsms.app.src
new file mode 100644
index 0000000..357ec77
--- /dev/null
+++ b/deps/gsms/src/gsms.app.src
@@ -0,0 +1,9 @@
+{application, gsms,
+ [
+ {description, "ETSI 03.40 Sender & Reciver"},
+ {vsn, git},
+ {registered, [gsms_router]},
+ {mod, {gsms_app,[]}},
+ {env, []},
+ {applications, [kernel,stdlib,uart]}
+ ]}.
diff --git a/deps/gsms/src/gsms.erl b/deps/gsms/src/gsms.erl
new file mode 100644
index 0000000..e682672
--- /dev/null
+++ b/deps/gsms/src/gsms.erl
@@ -0,0 +1,110 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%%
+%%% Created : 18 Apr 2013 by Tony Rogvall
+%%% @end
+
+-module(gsms).
+-include("gsms.hrl").
+
+%% Interface
+-export([send/2,
+ subscribe/1,
+ unsubscribe/1]).
+
+%% For testing
+%% @private
+-export([start/0,
+ stop/0]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%--------------------------------------------------------------------
+%% @doc
+%% Send an SMS.
+%% @end
+%%--------------------------------------------------------------------
+-spec send(Options::list({Key::atom(), Value::term()}), Body::string()) ->
+ {ok, Ref::reference()} |
+ {error, Reason::term()}.
+
+send(Opts, Body) ->
+ gsms_router:send(Opts, Body).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Subscribe to incoming SMS:s filtered by Filter.
+%% @end
+%%--------------------------------------------------------------------
+-spec subscribe(Filter::list(filter())) ->
+ {ok,Ref::reference()} |
+ {error,Reason::term()}.
+
+subscribe(Filter) ->
+ case lists:keytake(reg_exp, 1, Filter) of
+ {value, RegExp, Rest} when is_list(RegExp) ->
+ %% Convert to mp-format (and verify format)
+ case re:compile(RegExp, [unicode]) of
+ {ok, MP} ->
+ gsms_router:subscribe([{reg_exp, MP} | Rest]);
+ {error, _ErrSpec} = E->
+ E
+ end;
+ {value, _RegExp, _Rest} ->
+ %% Assuming mp-format
+ gsms_router:subscribe(Filter);
+ false ->
+ gsms_router:subscribe(Filter)
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Remove previous subscription refered to by Ref.
+%% @end
+%%--------------------------------------------------------------------
+-spec unsubscribe(Ref::reference()) -> ok.
+
+unsubscribe(Ref) ->
+ gsms_router:unsubscribe(Ref).
+
+%%%===================================================================
+start() ->
+ call([lager,ale,uart,gsms], start).
+
+stop() ->
+ call([gsms,uart,ale,lager], stop).
+
+call([App|Apps], F) ->
+ error_logger:info_msg("~p: ~p\n", [F,App]),
+ case application:F(App) of
+ {error,{not_started,App1}} ->
+ call([App1,App|Apps], F);
+ {error,{already_started,App}} ->
+ call(Apps, F);
+ ok ->
+ call(Apps, F);
+ Error ->
+ Error
+ end;
+call([],_F) ->
+ ok.
+
+
diff --git a/deps/gsms/src/gsms_0338.erl b/deps/gsms/src/gsms_0338.erl
new file mode 100644
index 0000000..6dadd5b
--- /dev/null
+++ b/deps/gsms/src/gsms_0338.erl
@@ -0,0 +1,370 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% GSM 03.38 codec
+%%% @end
+%%% Created : 16 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(gsms_0338).
+
+-export([decode/1, decode_char/1]).
+-export([encode/1, encode/2, encode_char/1]).
+
+decode(Bin) when is_binary(Bin) ->
+ decode_list(binary_to_list(Bin));
+decode(Cs) when is_list(Cs) ->
+ decode_list(Cs).
+
+%%
+decode_list([16#1B,C|Cs]) ->
+ case C of
+ 16#0A -> [16#000C|decode_list(Cs)]; %% FORM FEED
+ 16#14 -> [16#005E|decode_list(Cs)]; %% CIRCUMFLEX ACCENT
+ 16#28 -> [16#007B|decode_list(Cs)]; %% LEFT CURLY BRACKET
+ 16#29 -> [16#007D|decode_list(Cs)]; %% RIGHT CURLY BRACKET
+ 16#2F -> [16#005C|decode_list(Cs)]; %% REVERSE SOLIDUS
+ 16#3C -> [16#005B|decode_list(Cs)]; %% LEFT SQUARE BRACKET
+ 16#3D -> [16#007E|decode_list(Cs)]; %% TILDE
+ 16#3E -> [16#005D|decode_list(Cs)]; %% RIGHT SQUARE BRACKET
+ 16#40 -> [16#007C|decode_list(Cs)]; %% VERTICAL LINE
+ 16#65 -> [16#20AC|decode_list(Cs)]; %% EURO SIGN
+ _ -> [16#A0|decode_list(Cs)]
+ end;
+decode_list([C|Cs]) ->
+ [decode_char(C)|decode_list(Cs)];
+decode_list([]) ->
+ [].
+
+decode_char(C) ->
+ case C band 16#7f of
+ 16#00 -> 16#0040; %% COMMERCIAL AT
+ %%16#00 16#0000 %% NULL
+ 16#01 -> 16#00A3; %% POUND SIGN
+ 16#02 -> 16#0024; %% DOLLAR SIGN
+ 16#03 -> 16#00A5; %% YEN SIGN
+ 16#04 -> 16#00E8; %% LATIN SMALL LETTER E WITH GRAVE
+ 16#05 -> 16#00E9; %% LATIN SMALL LETTER E WITH ACUTE
+ 16#06 -> 16#00F9; %% LATIN SMALL LETTER U WITH GRAVE
+ 16#07 -> 16#00EC; %% LATIN SMALL LETTER I WITH GRAVE
+ 16#08 -> 16#00F2; %% LATIN SMALL LETTER O WITH GRAVE
+ 16#09 -> 16#00E7; %% LATIN SMALL LETTER C WITH CEDILLA
+ %%16#09 16#00C7 %% LATIN CAPITAL LETTER C WITH CEDILLA
+ 16#0A -> 16#000A; %% LINE FEED
+ 16#0B -> 16#00D8; %% LATIN CAPITAL LETTER O WITH STROKE
+ 16#0C -> 16#00F8; %% LATIN SMALL LETTER O WITH STROKE
+ 16#0D -> 16#000D; %% CARRIAGE RETURN
+ 16#0E -> 16#00C5; %% LATIN CAPITAL LETTER A WITH RING ABOVE
+ 16#0F -> 16#00E5; %% LATIN SMALL LETTER A WITH RING ABOVE
+ 16#10 -> 16#0394; %% GREEK CAPITAL LETTER DELTA
+ 16#11 -> 16#005F; %% LOW LINE
+ 16#12 -> 16#03A6; %% GREEK CAPITAL LETTER PHI
+ 16#13 -> 16#0393; %% GREEK CAPITAL LETTER GAMMA
+ 16#14 -> 16#039B; %% GREEK CAPITAL LETTER LAMDA
+ 16#15 -> 16#03A9; %% GREEK CAPITAL LETTER OMEGA
+ 16#16 -> 16#03A0; %% GREEK CAPITAL LETTER PI
+ 16#17 -> 16#03A8; %% GREEK CAPITAL LETTER PSI
+ 16#18 -> 16#03A3; %% GREEK CAPITAL LETTER SIGMA
+ 16#19 -> 16#0398; %% GREEK CAPITAL LETTER THETA
+ 16#1A -> 16#039E; %% GREEK CAPITAL LETTER XI
+ 16#1C -> 16#00C6; %% LATIN CAPITAL LETTER AE
+ 16#1D -> 16#00E6; %% LATIN SMALL LETTER AE
+ 16#1E -> 16#00DF; %% LATIN SMALL LETTER SHARP S (German)
+ 16#1F -> 16#00C9; %% LATIN CAPITAL LETTER E WITH ACUTE
+ 16#20 -> 16#0020; %% SPACE
+ 16#21 -> 16#0021; %% EXCLAMATION MARK
+ 16#22 -> 16#0022; %% QUOTATION MARK
+ 16#23 -> 16#0023; %% NUMBER SIGN
+ 16#24 -> 16#00A4; %% CURRENCY SIGN
+ 16#25 -> 16#0025; %% PERCENT SIGN
+ 16#26 -> 16#0026; %% AMPERSAND
+ 16#27 -> 16#0027; %% APOSTROPHE
+ 16#28 -> 16#0028; %% LEFT PARENTHESIS
+ 16#29 -> 16#0029; %% RIGHT PARENTHESIS
+ 16#2A -> 16#002A; %% ASTERISK
+ 16#2B -> 16#002B; %% PLUS SIGN
+ 16#2C -> 16#002C; %% COMMA
+ 16#2D -> 16#002D; %% HYPHEN-MINUS
+ 16#2E -> 16#002E; %% FULL STOP
+ 16#2F -> 16#002F; %% SOLIDUS
+ 16#30 -> 16#0030; %% DIGIT ZERO
+ 16#31 -> 16#0031; %% DIGIT ONE
+ 16#32 -> 16#0032; %% DIGIT TWO
+ 16#33 -> 16#0033; %% DIGIT THREE
+ 16#34 -> 16#0034; %% DIGIT FOUR
+ 16#35 -> 16#0035; %% DIGIT FIVE
+ 16#36 -> 16#0036; %% DIGIT SIX
+ 16#37 -> 16#0037; %% DIGIT SEVEN
+ 16#38 -> 16#0038; %% DIGIT EIGHT
+ 16#39 -> 16#0039; %% DIGIT NINE
+ 16#3A -> 16#003A; %% COLON
+ 16#3B -> 16#003B; %% SEMICOLON
+ 16#3C -> 16#003C; %% LESS-THAN SIGN
+ 16#3D -> 16#003D; %% EQUALS SIGN
+ 16#3E -> 16#003E; %% GREATER-THAN SIGN
+ 16#3F -> 16#003F; %% QUESTION MARK
+ 16#40 -> 16#00A1; %% INVERTED EXCLAMATION MARK
+ 16#41 -> 16#0041; %% LATIN CAPITAL LETTER A
+ %%16#41 16#0391 %% GREEK CAPITAL LETTER ALPHA
+ 16#42 -> 16#0042; %% LATIN CAPITAL LETTER B
+ %%16#42 16#0392 %% GREEK CAPITAL LETTER BETA
+ 16#43 -> 16#0043; %% LATIN CAPITAL LETTER C
+ 16#44 -> 16#0044; %% LATIN CAPITAL LETTER D
+ 16#45 -> 16#0045; %% LATIN CAPITAL LETTER E
+ %%16#45 16#0395 %% GREEK CAPITAL LETTER EPSILON
+ 16#46 -> 16#0046; %% LATIN CAPITAL LETTER F
+ 16#47 -> 16#0047; %% LATIN CAPITAL LETTER G
+ 16#48 -> 16#0048; %% LATIN CAPITAL LETTER H
+ %%16#48 16#0397 %% GREEK CAPITAL LETTER ETA
+ 16#49 -> 16#0049; %% LATIN CAPITAL LETTER I
+ %%16#49 16#0399 %% GREEK CAPITAL LETTER IOTA
+ 16#4A -> 16#004A; %% LATIN CAPITAL LETTER J
+ 16#4B -> 16#004B; %% LATIN CAPITAL LETTER K
+ %%16#4B 16#039A %% GREEK CAPITAL LETTER KAPPA
+ 16#4C -> 16#004C; %% LATIN CAPITAL LETTER L
+ 16#4D -> 16#004D; %% LATIN CAPITAL LETTER M
+ %%16#4D 16#039C %% GREEK CAPITAL LETTER MU
+ 16#4E -> 16#004E; %% LATIN CAPITAL LETTER N
+ %%16#4E 16#039D %% GREEK CAPITAL LETTER NU
+ 16#4F -> 16#004F; %% LATIN CAPITAL LETTER O
+ %%16#4F 16#039F %% GREEK CAPITAL LETTER OMICRON
+ 16#50 -> 16#0050; %% LATIN CAPITAL LETTER P
+ %%16#50 16#03A1 %% GREEK CAPITAL LETTER RHO
+ 16#51 -> 16#0051; %% LATIN CAPITAL LETTER Q
+ 16#52 -> 16#0052; %% LATIN CAPITAL LETTER R
+ 16#53 -> 16#0053; %% LATIN CAPITAL LETTER S
+ 16#54 -> 16#0054; %% LATIN CAPITAL LETTER T
+ %%16#54 16#03A4 %% GREEK CAPITAL LETTER TAU
+ 16#55 -> 16#0055; %% LATIN CAPITAL LETTER U
+ 16#56 -> 16#0056; %% LATIN CAPITAL LETTER V
+ 16#57 -> 16#0057; %% LATIN CAPITAL LETTER W
+ 16#58 -> 16#0058; %% LATIN CAPITAL LETTER X
+ %%16#58 16#03A7 %% GREEK CAPITAL LETTER CHI
+ 16#59 -> 16#0059; %% LATIN CAPITAL LETTER Y
+ %%16#59 16#03A5 %% GREEK CAPITAL LETTER UPSILON
+ 16#5A -> 16#005A; %% LATIN CAPITAL LETTER Z
+ %%16#5A 16#0396 %% GREEK CAPITAL LETTER ZETA
+ 16#5B -> 16#00C4; %% LATIN CAPITAL LETTER A WITH DIAERESIS
+ 16#5C -> 16#00D6; %% LATIN CAPITAL LETTER O WITH DIAERESIS
+ 16#5D -> 16#00D1; %% LATIN CAPITAL LETTER N WITH TILDE
+ 16#5E -> 16#00DC; %% LATIN CAPITAL LETTER U WITH DIAERESIS
+ 16#5F -> 16#00A7; %% SECTION SIGN
+ 16#60 -> 16#00BF; %% INVERTED QUESTION MARK
+ 16#61 -> 16#0061; %% LATIN SMALL LETTER A
+ 16#62 -> 16#0062; %% LATIN SMALL LETTER B
+ 16#63 -> 16#0063; %% LATIN SMALL LETTER C
+ 16#64 -> 16#0064; %% LATIN SMALL LETTER D
+ 16#65 -> 16#0065; %% LATIN SMALL LETTER E
+ 16#66 -> 16#0066; %% LATIN SMALL LETTER F
+ 16#67 -> 16#0067; %% LATIN SMALL LETTER G
+ 16#68 -> 16#0068; %% LATIN SMALL LETTER H
+ 16#69 -> 16#0069; %% LATIN SMALL LETTER I
+ 16#6A -> 16#006A; %% LATIN SMALL LETTER J
+ 16#6B -> 16#006B; %% LATIN SMALL LETTER K
+ 16#6C -> 16#006C; %% LATIN SMALL LETTER L
+ 16#6D -> 16#006D; %% LATIN SMALL LETTER M
+ 16#6E -> 16#006E; %% LATIN SMALL LETTER N
+ 16#6F -> 16#006F; %% LATIN SMALL LETTER O
+ 16#70 -> 16#0070; %% LATIN SMALL LETTER P
+ 16#71 -> 16#0071; %% LATIN SMALL LETTER Q
+ 16#72 -> 16#0072; %% LATIN SMALL LETTER R
+ 16#73 -> 16#0073; %% LATIN SMALL LETTER S
+ 16#74 -> 16#0074; %% LATIN SMALL LETTER T
+ 16#75 -> 16#0075; %% LATIN SMALL LETTER U
+ 16#76 -> 16#0076; %% LATIN SMALL LETTER V
+ 16#77 -> 16#0077; %% LATIN SMALL LETTER W
+ 16#78 -> 16#0078; %% LATIN SMALL LETTER X
+ 16#79 -> 16#0079; %% LATIN SMALL LETTER Y
+ 16#7A -> 16#007A; %% LATIN SMALL LETTER Z
+ 16#7B -> 16#00E4; %% LATIN SMALL LETTER A WITH DIAERESIS
+ 16#7C -> 16#00F6; %% LATIN SMALL LETTER O WITH DIAERESIS
+ 16#7D -> 16#00F1; %% LATIN SMALL LETTER N WITH TILDE
+ 16#7E -> 16#00FC; %% LATIN SMALL LETTER U WITH DIAERESIS
+ 16#7F -> 16#00E0 %% LATIN SMALL LETTER A WITH GRAVE
+ end.
+
+encode(Data) ->
+ encode(Data, -1).
+
+encode(Bin,Max) when is_binary(Bin), is_integer(Max) ->
+ encode_list(binary_to_list(Bin), Max);
+encode(Cs,Max) when is_list(Cs), is_integer(Max) ->
+ encode_list(Cs,Max).
+
+
+encode_list(Cs,Max) ->
+ encode_list_(Cs,Max,[]).
+
+encode_list_(Cs,0,Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)),Cs};
+encode_list_(Cs1=[C|Cs],I,Acc) ->
+ try encode_char(C) of
+ [Esc,Y] ->
+ if I >= 2 -> encode_list_(Cs,I-2,[Y,Esc|Acc]);
+ true -> {iolist_to_binary(lists:reverse(Acc)),Cs1}
+ end;
+ Y -> encode_list_(Cs,I-1,[Y|Acc])
+ catch
+ error:_ ->
+ encode_list_(Cs,I-2,[16#A0,16#1B|Acc])
+ end;
+encode_list_([],_I,Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)),[]}.
+
+
+encode_char(C) ->
+ case C of
+ 16#0040 -> 16#00; %% COMMERCIAL AT
+ 16#0000 -> 16#00; %% NULL
+ 16#00A3 -> 16#01; %% POUND SIGN
+ 16#0024 -> 16#02; %% DOLLAR SIGN
+ 16#00A5 -> 16#03; %% YEN SIGN
+ 16#00E8 -> 16#04; %% LATIN SMALL LETTER E WITH GRAVE
+ 16#00E9 -> 16#05; %% LATIN SMALL LETTER E WITH ACUTE
+ 16#00F9 -> 16#06; %% LATIN SMALL LETTER U WITH GRAVE
+ 16#00EC -> 16#07; %% LATIN SMALL LETTER I WITH GRAVE
+ 16#00F2 -> 16#08; %% LATIN SMALL LETTER O WITH GRAVE
+ 16#00E7 -> 16#09; %% LATIN SMALL LETTER C WITH CEDILLA
+ 16#00C7 -> 16#09; %% LATIN CAPITAL LETTER C WITH CEDILLA
+ 16#000A -> 16#0A; %% LINE FEED
+ 16#00D8 -> 16#0B; %% LATIN CAPITAL LETTER O WITH STROKE
+ 16#00F8 -> 16#0C; %% LATIN SMALL LETTER O WITH STROKE
+ 16#000D -> 16#0D; %% CARRIAGE RETURN
+ 16#00C5 -> 16#0E; %% LATIN CAPITAL LETTER A WITH RING ABOVE
+ 16#00E5 -> 16#0F; %% LATIN SMALL LETTER A WITH RING ABOVE
+ 16#0394 -> 16#10; %% GREEK CAPITAL LETTER DELTA
+ 16#005F -> 16#11; %% LOW LINE
+ 16#03A6 -> 16#12; %% GREEK CAPITAL LETTER PHI
+ 16#0393 -> 16#13; %% GREEK CAPITAL LETTER GAMMA
+ 16#039B -> 16#14; %% GREEK CAPITAL LETTER LAMDA
+ 16#03A9 -> 16#15; %% GREEK CAPITAL LETTER OMEGA
+ 16#03A0 -> 16#16; %% GREEK CAPITAL LETTER PI
+ 16#03A8 -> 16#17; %% GREEK CAPITAL LETTER PSI
+ 16#03A3 -> 16#18; %% GREEK CAPITAL LETTER SIGMA
+ 16#0398 -> 16#19; %% GREEK CAPITAL LETTER THETA
+ 16#039E -> 16#1A; %% GREEK CAPITAL LETTER XI
+ 16#00C6 -> 16#1C; %% LATIN CAPITAL LETTER AE
+ 16#00E6 -> 16#1D; %% LATIN SMALL LETTER AE
+ 16#00DF -> 16#1E; %% LATIN SMALL LETTER SHARP S (German)
+ 16#00C9 -> 16#1F; %% LATIN CAPITAL LETTER E WITH ACUTE
+ 16#0020 -> 16#20; %% SPACE
+ 16#0021 -> 16#21; %% EXCLAMATION MARK
+ 16#0022 -> 16#22; %% QUOTATION MARK
+ 16#0023 -> 16#23; %% NUMBER SIGN
+ 16#00A4 -> 16#24; %% CURRENCY SIGN
+ 16#0025 -> 16#25; %% PERCENT SIGN
+ 16#0026 -> 16#26; %% AMPERSAND
+ 16#0027 -> 16#27; %% APOSTROPHE
+ 16#0028 -> 16#28; %% LEFT PARENTHESIS
+ 16#0029 -> 16#29; %% RIGHT PARENTHESIS
+ 16#002A -> 16#2A; %% ASTERISK
+ 16#002B -> 16#2B; %% PLUS SIGN
+ 16#002C -> 16#2C; %% COMMA
+ 16#002D -> 16#2D; %% HYPHEN-MINUS
+ 16#002E -> 16#2E; %% FULL STOP
+ 16#002F -> 16#2F; %% SOLIDUS
+ 16#0030 -> 16#30; %% DIGIT ZERO
+ 16#0031 -> 16#31; %% DIGIT ONE
+ 16#0032 -> 16#32; %% DIGIT TWO
+ 16#0033 -> 16#33; %% DIGIT THREE
+ 16#0034 -> 16#34; %% DIGIT FOUR
+ 16#0035 -> 16#35; %% DIGIT FIVE
+ 16#0036 -> 16#36; %% DIGIT SIX
+ 16#0037 -> 16#37; %% DIGIT SEVEN
+ 16#0038 -> 16#38; %% DIGIT EIGHT
+ 16#0039 -> 16#39; %% DIGIT NINE
+ 16#003A -> 16#3A; %% COLON
+ 16#003B -> 16#3B; %% SEMICOLON
+ 16#003C -> 16#3C; %% LESS-THAN SIGN
+ 16#003D -> 16#3D; %% EQUALS SIGN
+ 16#003E -> 16#3E; %% GREATER-THAN SIGN
+ 16#003F -> 16#3F; %% QUESTION MARK
+ 16#00A1 -> 16#40; %% INVERTED EXCLAMATION MARK
+ 16#0041 -> 16#41; %% LATIN CAPITAL LETTER A
+ 16#0391 -> 16#41; %% GREEK CAPITAL LETTER ALPHA
+ 16#0042 -> 16#42; %% LATIN CAPITAL LETTER B
+ 16#0392 -> 16#42; %% GREEK CAPITAL LETTER BETA
+ 16#0043 -> 16#43; %% LATIN CAPITAL LETTER C
+ 16#0044 -> 16#44; %% LATIN CAPITAL LETTER D
+ 16#0045 -> 16#45; %% LATIN CAPITAL LETTER E
+ 16#0395 -> 16#45; %% GREEK CAPITAL LETTER EPSILON
+ 16#0046 -> 16#46; %% LATIN CAPITAL LETTER F
+ 16#0047 -> 16#47; %% LATIN CAPITAL LETTER G
+ 16#0048 -> 16#48; %% LATIN CAPITAL LETTER H
+ 16#0397 -> 16#48; %% GREEK CAPITAL LETTER ETA
+ 16#0049 -> 16#49; %% LATIN CAPITAL LETTER I
+ 16#0399 -> 16#49; %% GREEK CAPITAL LETTER IOTA
+ 16#004A -> 16#4A; %% LATIN CAPITAL LETTER J
+ 16#004B -> 16#4B; %% LATIN CAPITAL LETTER K
+ 16#039A -> 16#4B; %% GREEK CAPITAL LETTER KAPPA
+ 16#004C -> 16#4C; %% LATIN CAPITAL LETTER L
+ 16#004D -> 16#4D; %% LATIN CAPITAL LETTER M
+ 16#039C -> 16#4D; %% GREEK CAPITAL LETTER MU
+ 16#004E -> 16#4E; %% LATIN CAPITAL LETTER N
+ 16#039D -> 16#4E; %% GREEK CAPITAL LETTER NU
+ 16#004F -> 16#4F; %% LATIN CAPITAL LETTER O
+ 16#039F -> 16#4F; %% GREEK CAPITAL LETTER OMICRON
+ 16#0050 -> 16#50; %% LATIN CAPITAL LETTER P
+ 16#03A1 -> 16#50; %% GREEK CAPITAL LETTER RHO
+ 16#0051 -> 16#51; %% LATIN CAPITAL LETTER Q
+ 16#0052 -> 16#52; %% LATIN CAPITAL LETTER R
+ 16#0053 -> 16#53; %% LATIN CAPITAL LETTER S
+ 16#0054 -> 16#54; %% LATIN CAPITAL LETTER T
+ 16#03A4 -> 16#54; %% GREEK CAPITAL LETTER TAU
+ 16#0055 -> 16#55; %% LATIN CAPITAL LETTER U
+ 16#0056 -> 16#56; %% LATIN CAPITAL LETTER V
+ 16#0057 -> 16#57; %% LATIN CAPITAL LETTER W
+ 16#0058 -> 16#58; %% LATIN CAPITAL LETTER X
+ 16#03A7 -> 16#58; %% GREEK CAPITAL LETTER CHI
+ 16#0059 -> 16#59; %% LATIN CAPITAL LETTER Y
+ 16#03A5 -> 16#59; %% GREEK CAPITAL LETTER UPSILON
+ 16#005A -> 16#5A; %% LATIN CAPITAL LETTER Z
+ 16#0396 -> 16#5A; %% GREEK CAPITAL LETTER ZETA
+ 16#00C4 -> 16#5B; %% LATIN CAPITAL LETTER A WITH DIAERESIS
+ 16#00D6 -> 16#5C; %% LATIN CAPITAL LETTER O WITH DIAERESIS
+ 16#00D1 -> 16#5D; %% LATIN CAPITAL LETTER N WITH TILDE
+ 16#00DC -> 16#5E; %% LATIN CAPITAL LETTER U WITH DIAERESIS
+ 16#00A7 -> 16#5F; %% SECTION SIGN
+ 16#00BF -> 16#60; %% INVERTED QUESTION MARK
+ 16#0061 -> 16#61; %% LATIN SMALL LETTER A
+ 16#0062 -> 16#62; %% LATIN SMALL LETTER B
+ 16#0063 -> 16#63; %% LATIN SMALL LETTER C
+ 16#0064 -> 16#64; %% LATIN SMALL LETTER D
+ 16#0065 -> 16#65; %% LATIN SMALL LETTER E
+ 16#0066 -> 16#66; %% LATIN SMALL LETTER F
+ 16#0067 -> 16#67; %% LATIN SMALL LETTER G
+ 16#0068 -> 16#68; %% LATIN SMALL LETTER H
+ 16#0069 -> 16#69; %% LATIN SMALL LETTER I
+ 16#006A -> 16#6A; %% LATIN SMALL LETTER J
+ 16#006B -> 16#6B; %% LATIN SMALL LETTER K
+ 16#006C -> 16#6C; %% LATIN SMALL LETTER L
+ 16#006D -> 16#6D; %% LATIN SMALL LETTER M
+ 16#006E -> 16#6E; %% LATIN SMALL LETTER N
+ 16#006F -> 16#6F; %% LATIN SMALL LETTER O
+ 16#0070 -> 16#70; %% LATIN SMALL LETTER P
+ 16#0071 -> 16#71; %% LATIN SMALL LETTER Q
+ 16#0072 -> 16#72; %% LATIN SMALL LETTER R
+ 16#0073 -> 16#73; %% LATIN SMALL LETTER S
+ 16#0074 -> 16#74; %% LATIN SMALL LETTER T
+ 16#0075 -> 16#75; %% LATIN SMALL LETTER U
+ 16#0076 -> 16#76; %% LATIN SMALL LETTER V
+ 16#0077 -> 16#77; %% LATIN SMALL LETTER W
+ 16#0078 -> 16#78; %% LATIN SMALL LETTER X
+ 16#0079 -> 16#79; %% LATIN SMALL LETTER Y
+ 16#007A -> 16#7A; %% LATIN SMALL LETTER Z
+ 16#00E4 -> 16#7B; %% LATIN SMALL LETTER A WITH DIAERESIS
+ 16#00F6 -> 16#7C; %% LATIN SMALL LETTER O WITH DIAERESIS
+ 16#00F1 -> 16#7D; %% LATIN SMALL LETTER N WITH TILDE
+ 16#00FC -> 16#7E; %% LATIN SMALL LETTER U WITH DIAERESIS
+ 16#00E0 -> 16#7F; %% LATIN SMALL LETTER A WITH GRAVE
+ %% extensions
+ 16#000C -> [16#1B,16#0A]; %% FORM FEED
+ 16#005E -> [16#1B,16#14]; %% CIRCUMFLEX ACCENT
+ 16#007B -> [16#1B,16#28]; %% LEFT CURLY BRACKET
+ 16#007D -> [16#1B,16#29]; %% RIGHT CURLY BRACKET
+ 16#005C -> [16#1B,16#2F]; %% REVERSE SOLIDUS
+ 16#005B -> [16#1B,16#3C]; %% LEFT SQUARE BRACKET
+ 16#007E -> [16#1B,16#3D]; %% TILDE
+ 16#005D -> [16#1B,16#3E]; %% RIGHT SQUARE BRACKET
+ 16#007C -> [16#1B,16#40]; %% VERTICAL LINE
+ 16#20AC -> [16#1B,16#65] %% EURO SIGN
+ end.
diff --git a/deps/gsms/src/gsms_0705.erl b/deps/gsms/src/gsms_0705.erl
new file mode 100644
index 0000000..8756d3c
--- /dev/null
+++ b/deps/gsms/src/gsms_0705.erl
@@ -0,0 +1,972 @@
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% SMS service using gsms_uart to access 07.05 commands
+%%% @end
+%%% Created : 24 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(gsms_0705).
+
+-behaviour(gen_server).
+
+-export([start/2]).
+-export([start_link/2]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% api from gsms_router
+-export([send/3]). %% send straight
+-export([write/3]). %% write to sim but do not send
+-export([scan_input/1]).
+
+
+%% access 07.05 commands
+-export([get_version/1]).
+-export([get_manufacturer/1, get_model/1]).
+-export([get_imei/1, get_msisdn/1, get_imsi/1]).
+-export([get_activity_status/1]).
+-export([get_network_registration_status/1]).
+-export([get_signal_strength/1]).
+-export([get_battery_status/1]).
+-export([get_smsc/1]).
+
+-export([list_unread_messages/1]).
+-export([list_read_messages/1]).
+-export([list_unsent_messages/1]).
+-export([list_sent_messages/1]).
+-export([list_all_messages/1]).
+-export([list_indices/1]).
+
+-export([delete_message/2]).
+-export([delete_read_messages/1]).
+-export([delete_sent_messages/1]).
+-export([delete_unsent_messages/1]).
+-export([delete_all_messages/1]).
+
+-export([read_message/2]).
+
+%% access 07.05 commands given the driver pid
+-export([drv_reset/1]).
+-export([drv_init_csms_service/1]).
+-export([drv_check_csms_capability/1]).
+-export([drv_set_csms_notification/1]).
+-export([drv_set_csms_pdu_mode/1]).
+-export([drv_get_version/1]).
+-export([drv_get_manufacturer/1, drv_get_model/1]).
+-export([drv_get_imei/1, drv_get_msisdn/1, drv_get_imsi/1]).
+-export([drv_get_activity_status/1]).
+-export([drv_get_network_registration_status/1]).
+-export([drv_get_signal_strength/1]).
+-export([drv_get_battery_status/1]).
+-export([drv_get_smsc/1]).
+
+-export([drv_list_unread_messages/1]).
+-export([drv_list_read_messages/1]).
+-export([drv_list_unsent_messages/1]).
+-export([drv_list_sent_messages/1]).
+-export([drv_list_all_messages/1]).
+-export([drv_list_indices/1]).
+
+-export([drv_delete_message/2]).
+-export([drv_delete_message/3]).
+-export([drv_delete_all_message/1]).
+-export([drv_read_message/2]).
+-export([drv_write_message/3]).
+-export([drv_send_message/3]).
+
+-include("../include/gsms.hrl").
+
+-type qkey_t() :: {#gsms_addr{},MRef::integer}.
+-type isegment_t() :: {I::integer(),Ix::integer(),Pdu::#gsms_deliver_pdu{}}.
+-type qelem_t() :: {qkey_t(),TRef::reference(),N::integer,[isegment_t()]}.
+
+-type osegment_t() :: {send | write,
+ I::integer,
+ N::integer,
+ SRef::reference(),
+ Notify::boolean(),
+ Sender::pid(),
+ Pdu::#gsms_submit_pdu{}}.
+
+-type uart_driver() :: pid() | atom().
+
+-define(DEFAULT_SEND_DELAY, 0).
+-define(DEFAULT_SEGMENT_TIMEOUT, 60000).
+-define(DEFAULT_CONCAT_8BIT, true).
+-define(DEFAULT_CONCAT_SEQUENCE, true).
+-define(DEFAULT_CONCAT_REF, 0).
+-define(DEFAULT_SIMPIN, "").
+
+-type gsms_option() ::
+ {simpin, Pin::string()} |
+ {bnumber, Number::string()} |
+ {attributes, [{Key::atom(),Value::term()}]} |
+ {segment_timeout, Timeout::timeout()} |
+ {send_delay, Delay::timeout()} |
+ {concat_8bit, EightBit :: boolean()} |
+ {concat_seq, Sequential :: boolean()}
+ .
+
+-type gsms_send_option() ::
+ {ref, ConCatRef::uint16()} |
+ {notify, boolean()}.
+
+-record(state,
+ {
+ id :: integer(), %% id in gsms_router
+ drv :: pid(), %% pid of the gsms_uart AT driver
+ ref :: reference(), %% notification reference
+ %% state
+ drv_up = false, %% drv ready to process
+ sending = false :: boolean(), %% sending message outstanding?
+ inq = [] :: [qelem_t()],
+ outq = [] :: [osegment_t()],
+ concat_ref = ?DEFAULT_CONCAT_REF :: integer(),
+ %% config
+ bnumber = "" :: string(), %% modem phone number
+ simpin = "" :: string(), %% SIM pin when needed
+ segment_timeout :: timeout(),%% max wait for segment
+ send_delay :: timeout(), %% delay between sending segments
+ concat_seq :: boolean(), %% concat ref is sequence or random
+ concat_8bit :: boolean(), %% 8bit or 16bit
+ attributes = [] :: [{atom(),term()}]
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec send(Pid::pid(), Opts::[gsms_pdu_option()|gsms_send_option()],
+ Message::list()) ->
+ {ok, Ref::reference()} | {error, Reason::term()}.
+
+%% send options: pdu_options ++ [{notify,boolean()},{ref,ConCatRef}]
+
+send(Pid, Opts, Message) ->
+ gen_server:call(Pid, {send, self(), Opts, Message}).
+
+-spec write(Pid::pid(),
+ Opts::[gsms_pdu_option()|gsms_send_option()],
+ Message::list()) ->
+ {ok, Ref::reference()} | {error, Reason::term()}.
+
+write(Pid, Opts, Message) ->
+ gen_server:call(Pid, {write, self(), Opts, Message}).
+
+%%
+%% Check if we have messages to process in the inbox
+%%
+scan_input(Pid) ->
+ Pid ! scan_input.
+
+get_version(Pid) ->
+ gen_server:call(Pid, get_version).
+get_manufacturer(Pid) ->
+ gen_server:call(Pid, get_manufacturer).
+get_model(Pid) ->
+ gen_server:call(Pid, get_model).
+get_imei(Pid) ->
+ gen_server:call(Pid, get_imei).
+get_msisdn(Pid) ->
+ gen_server:call(Pid, get_msisdn).
+get_imsi(Pid) ->
+ gen_server:call(Pid, get_imsi).
+get_activity_status(Pid) ->
+ gen_server:call(Pid, get_activity_status).
+get_network_registration_status(Pid) ->
+ gen_server:call(Pid, get_network_registration_status).
+get_signal_strength(Pid) ->
+ gen_server:call(Pid, get_signal_strength).
+get_battery_status(Pid) ->
+ gen_server:call(Pid, get_battery_status).
+get_smsc(Pid) ->
+ gen_server:call(Pid, get_smsc).
+
+list_unread_messages(Pid) ->
+ gen_server:call(Pid, list_unread_messages).
+list_read_messages(Pid) ->
+ gen_server:call(Pid, list_read_messages).
+list_unsent_messages(Pid) ->
+ gen_server:call(Pid, list_unsent_messages).
+list_sent_messages(Pid) ->
+ gen_server:call(Pid, list_sent_messages).
+list_all_messages(Pid) ->
+ gen_server:call(Pid, list_all_messages).
+list_indices(Pid) ->
+ gen_server:call(Pid, list_indices).
+
+delete_message(Pid, I) when is_integer(I), I>=0 ->
+ gen_server:call(Pid, {delete_message, I}).
+delete_read_messages(Pid) ->
+ gen_server:call(Pid, {delete_messages,1}).
+delete_sent_messages(Pid) -> %% and read messages
+ gen_server:call(Pid, {delete_messages,2}).
+delete_unsent_messages(Pid) -> %% and read,sent messages
+ gen_server:call(Pid, {delete_messages,3}).
+delete_all_messages(Pid) ->
+ gen_server:call(Pid, {delete_messages,4}).
+
+read_message(Pid,I) when is_integer(I), I>=0 ->
+ gen_server:call(Pid, {read_messages,I}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start(Options) -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+
+-spec start(Id::integer(),Opts::[gsms_option()]) ->
+ {ok,pid()} | {error,Reason::term()}.
+
+start(Id, Opts) when is_integer(Id), is_list(Opts) ->
+ gsms:start(),
+ ChildSpec= {{?MODULE,Id}, {?MODULE, start_link, [Id,Opts]},
+ permanent, 5000, worker, [?MODULE]},
+ supervisor:start_child(gsms_if_sup, ChildSpec).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Id, Opts) ->
+ gen_server:start_link(?MODULE, [Id,Opts], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([Id,Opts]) ->
+ State0 = #state { simpin = ?DEFAULT_SIMPIN,
+ segment_timeout = ?DEFAULT_SEGMENT_TIMEOUT,
+ send_delay = ?DEFAULT_SEND_DELAY,
+ concat_seq = ?DEFAULT_CONCAT_SEQUENCE,
+ concat_8bit = ?DEFAULT_CONCAT_8BIT },
+ {Opts1,State1} = setopts(Opts, State0),
+ {ok,Pid} = gsms_uart:start_link(Opts1),
+ {ok,Ref} = gsms_uart:subscribe(Pid), %% subscribe to all events
+ {ok, State1#state { id = Id,
+ drv = Pid,
+ ref = Ref }}.
+
+%%--------------------------------------------------------------------
+%% @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({send,Sender,Opts,Body}, _From, State) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ {CRef,Opts1} = next_concat_ref(Opts,State),
+ Notify = proplists:get_bool(notify, Opts1),
+ N = length(PduList),
+ SRef = make_ref(),
+ OutQ = State#state.outq ++
+ [{send,I,N,SRef,Notify,Sender,Pdu} ||
+ {Pdu,I} <- lists:zip(PduList, lists:seq(1,N))],
+ State1 = sending(OutQ,State),
+ {reply, {ok,SRef}, State1#state { concat_ref = CRef}};
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({write,Sender,Opts,Body}, _From, State) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ {CRef,Opts1} = next_concat_ref(Opts,State),
+ Notify = proplists:get_bool(notify, Opts1),
+ N = length(PduList),
+ WRef = make_ref(),
+ OutQ = State#state.outq ++
+ [{write,I,N,WRef,Notify,Sender,Pdu} ||
+ {Pdu,I} <- lists:zip(PduList, lists:seq(1,N))],
+ State1 = sending(OutQ,State),
+ {reply, {ok,WRef}, State1#state { concat_ref = CRef}};
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({cancel,Ref}, _From, State) ->
+ %% Remove and segments not sent for SRef
+ %% Possibly sent a cancel command ? if supported
+ OutQ =
+ lists:foldr(
+ fun(E={_Operation,_I,_N,Ref1,_Notify,_Sender,_Pdu}, Acc) ->
+ if Ref1 =:= Ref -> Acc;
+ true -> [E|Acc]
+ end
+ end, [], State#state.outq),
+ {reply, ok, State#state { outq = OutQ }};
+
+handle_call(get_version, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_version(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_manufacturer, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_manufacturer(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_model, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_model(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_imei, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_imei(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_msisdn, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_msisdn(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_imsi, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_imsi(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_network_registration_status, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_network_registration_status(State#state.drv),
+ State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_signal_strength, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_signal_strength(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_battery_status, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_battery_status(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call(get_smsc, _From, State) ->
+ if State#state.drv_up ->
+ {reply, drv_get_smsc(State#state.drv), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call({list_messages,N},_From,State) ->
+ if State#state.drv_up ->
+ {reply, drv_list_messages(State#state.drv, N), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call({delete_message,I},_From,State) ->
+ if State#state.drv_up ->
+ {reply, drv_delete_message(State#state.drv,I), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+handle_call({delete_messages,F},_From,State) when F>0 ->
+ if State#state.drv_up ->
+ {reply, drv_delete_message(State#state.drv,0,F), State};
+ true ->
+ {reply, {error, not_up}, State}
+ end;
+
+
+handle_call(_Request, _From, State) ->
+ 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) ->
+ {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({gsms_event,Ref,Event}, State) when State#state.ref =:= Ref ->
+ case Event of
+ {cmti,[{"store",_Name},{"index", Ix}]} ->
+ %% read a stored message
+ case drv_read_message(State#state.drv, Ix) of
+ {ok, Sms} ->
+ lager:debug("read_message: ~p", [Sms]),
+ State1 = handle_sms(Sms, Ix, State),
+ {noreply, State1};
+ Error ->
+ lager:info("read_message failed: ~p\n", [Error]),
+ {noreply, State}
+ end;
+ {data,"+CREG:"++Params} ->
+ case erl_scan:string(Params) of
+ {ok,[{integer,_,Status}|_],_} ->
+ %% we do not use the the access value
+ gsms_router:input_from(State#state.bnumber,{creg,Status}),
+ {noreply, State};
+ _ ->
+ lager:info("event ignored ~p\n", [Event]),
+ {noreply, State}
+ end;
+ _ ->
+ lager:info("event ignored ~p\n", [Event]),
+ {noreply, State}
+ end;
+handle_info({gsms_uart, Pid, up}, State) when State#state.drv =:= Pid ->
+ %% gsms_uart is up and running
+ %% FIXME: check if sim is locked & unlock if possible
+ gsms_uart:at(Pid,"E0"), %% disable echo (again)
+ timer:sleep(100), %% help?
+ ok = drv_check_csms_capability(Pid),
+ ok = drv_set_csms_pdu_mode(Pid),
+ ok = drv_set_csms_notification(Pid),
+ BNumber = if State#state.bnumber =:= "" ->
+ case drv_get_msisdn(Pid) of
+ ok -> "";
+ {ok,B} -> B
+ end;
+ true ->
+ State#state.bnumber
+ end,
+ State1 = State#state { bnumber = BNumber, drv_up = true },
+ ok = gsms_router:join(BNumber, State1#state.attributes),
+ %% make sure we scan messages that arrived while we where gone,
+ scan_input(self()),
+ lager:debug("running state = ~p", [State1]),
+ {noreply, State1};
+
+handle_info({timeout,TRef,{cancel,Key}}, State) ->
+ %% reject incoming message because of timeout
+ Q = State#state.inq,
+ case lists:keytake(Key, 1, Q) of
+ false ->
+ lager:warning("message from ~p not present in timeout", [Key]),
+ {noreply, State};
+ {value,{_,TRef,_N,Segments},Q0} ->
+ lager:warning("message from ~p dropped, timeout", [Key]),
+ lists:foreach(
+ fun({_I,Ix,_}) ->
+ drv_delete_message(State#state.drv, Ix)
+ end, Segments),
+ {noreply, State#state {inq = Q0}}
+ end;
+handle_info(scan_input, State) ->
+ %% scan_input should be run after startup to get buffered
+ %% messages stored in SIM card while application was down
+ case drv_list_indices(State#state.drv) of
+ {ok,[Ixs | _]} ->
+ Pid = self(),
+ Ref = State#state.ref,
+ lists:foreach(
+ fun(Ix) ->
+ Event = {cmti,[{"store","SM"},{"index",Ix}]},
+ Pid ! {gsms_event,Ref,Event}
+ end, expand_index_list(Ixs)),
+ {noreply, State};
+ Reply ->
+ lager:debug("list_indices reply ~p", [Reply]),
+ {noreply, State}
+ end;
+handle_info(send, State) ->
+ %% send next PDU
+ case State#state.outq of
+ [{send,I,N,SRef,Notify,Sender,Pdu}|OutQ] ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ Reply =
+ gsms_uart:atd(State#state.drv,
+ "+CMGS="++integer_to_list(Len),Hex),
+ lager:debug("send status segment ~w of ~w response=~p\n",
+ [I,N,Reply]),
+ %% Fixme handle Reply=error!!! cancel rest of segments etc
+ if I =:= N, Notify =:= true ->
+ Sender ! {gsms_notify, SRef, ok};
+ true ->
+ ok
+ end,
+ {noreply, sending(OutQ, State)};
+ [{write,I,N,SRef,Notify,Sender,Pdu}|OutQ] ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ Reply = gsms_uart:atd(State#state.drv,
+ "+CMGW="++integer_to_list(Len),Hex),
+ lager:debug("wrote status segment ~w of ~w response=~p\n",
+ [I,N,Reply]),
+ %% Fixme handle Reply=error!!! cancel rest of segments etc
+ if I =:= N, Notify -> %% assume ok
+ Sender ! {gsms_notify, SRef, ok};
+ true ->
+ ok
+ end,
+ {noreply, sending(OutQ, State)};
+ [] ->
+ {noreply, State#state { sending = false }}
+ end;
+handle_info(_Info, State) ->
+ {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
+%%%===================================================================
+
+
+-spec drv_reset(Drv::uart_driver()) -> ok.
+
+drv_reset(Drv) ->
+ gsms_uart:at(Drv,"Z"),
+ gsms_uart:at(Drv,"E0"),
+ drv_set_csms_pdu_mode(Drv),
+ drv_set_csms_notification(Drv).
+
+
+-spec drv_init_csms_service(Drv::uart_driver()) -> ok.
+drv_init_csms_service(Drv) ->
+ ok = drv_check_csms_capability(Drv),
+ ok = drv_set_csms_pdu_mode(Drv),
+ ok = drv_set_csms_notification(Drv),
+ %% how do we read the stored pdu like they where receive and when?
+ %% trigger automatic read of stored SMS when a subsrciber is
+ %% registered? subscriber need to ack in order to delete the
+ %% stored SMS
+ ok.
+
+
+-spec drv_check_csms_capability(Drv::uart_driver()) -> ok.
+drv_check_csms_capability(Drv) ->
+ case gsms_uart:at(Drv,"+CSMS=0") of
+ {ok, "+CSMS:"++Storage} ->
+ lager:debug("sms_capability: +CSMS: ~s", [Storage]);
+ Error ->
+ Error
+ end.
+
+-spec drv_set_csms_pdu_mode(Drv::uart_driver()) -> ok.
+
+drv_set_csms_pdu_mode(Drv) ->
+ gsms_uart:at(Drv,"+CMGF=0").
+
+%% AT+CNMI=1,1,0,0,0 Set the new message indicators.
+%%
+%% AT+CNMI=<mode>,<mt>,<bm>,<ds>,<bfr>
+%%
+%% <mode>=1 discard unsolicited result codes indication when TA –
+%% TE link is reserved.
+%% <mt>=1 SMS-DELIVERs are delivered to the SIM and routed using
+%% unsolicited code.
+%% <bm>=0 no CBM indications are routed to the TE.
+%% <ds>=0 no SMS-STATUS-REPORTs are routed.
+%% <bfr>=0 TA buffer of unsolicited result codes defined within this
+%% command is flushed to the TE.
+%% OK Modem Response.
+drv_set_csms_notification(Drv) ->
+ gsms_uart:at(Drv,"+CNMI=1,1,0,0,0").
+
+%% pick up information about various things
+drv_get_version(Drv) ->
+ gsms_uart:at(Drv,"+CGMR").
+
+drv_get_manufacturer(Drv) ->
+ gsms_uart:at(Drv,"+CGMI").
+
+drv_get_model(Drv) ->
+ gsms_uart:at(Drv,"+CGMM").
+
+drv_get_imei(Drv) ->
+ gsms_uart:at(Drv,"+CGSN").
+
+drv_get_msisdn(Drv) ->
+ gsms_uart:at(Drv,"+CNUM").
+
+drv_get_imsi(Drv) ->
+ gsms_uart:at(Drv,"+CIMI").
+
+drv_get_activity_status(Drv) ->
+ gsms_uart:eat(Drv,"+CPAS").
+
+drv_get_network_registration_status(Drv) ->
+ gsms_uart:at(Drv,"+CREG?").
+
+drv_get_signal_strength(Drv) ->
+ gsms_uart:at(Drv,"+CSQ").
+
+drv_get_battery_status(Drv) ->
+ gsms_uart:at(Drv,"+CBC").
+
+drv_get_smsc(Drv) ->
+ gsms_uart:at(Drv, "+CSCA?").
+
+%% SMS commands
+
+drv_list_unread_messages(Drv) ->
+ drv_list_messages(Drv, 0).
+drv_list_read_messages(Drv) ->
+ drv_list_messages(Drv, 1).
+drv_list_unsent_messages(Drv) ->
+ drv_list_messages(Drv, 2).
+drv_list_sent_messages(Drv) ->
+ drv_list_messages(Drv, 3).
+drv_list_all_messages(Drv) ->
+ drv_list_messages(Drv, 4).
+
+drv_list_messages(Drv, N) when is_integer(N), N>=0, N=<4 ->
+ gsms_uart:at(Drv, "+CMGL="++integer_to_list(N)).
+
+%% message list
+drv_list_indices(Drv) ->
+ case gsms_uart:at(Drv,"+CMGD=?") of
+ {ok,"+CMGD:"++Params} ->
+ case erl_scan:string(Params) of
+ {ok,Ts,_} ->
+ parse_index_lists(Ts);
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+
+%% +CMGD=I == +CMDG=I,0 only delete message I
+%% +CMGD=I,1 == +CMGD=0,1 delete ALL "read" messages
+%% +CMGD=I,2 == +CMGD=0,2 delete ALL "read","sent" messages
+%% +CMGD=I,3 == +CMGD=0,3 delete ALL "read","sent", "unsent" messages
+%% +CMFD=I,4 == +CMGD=0,4 delete ALL messages
+
+drv_delete_message(Drv,I) when is_integer(I), I>=0 ->
+ drv_delete_message(Drv,I,0).
+
+drv_delete_message(Drv,I,F) when is_integer(I), I>=0, F>=0,F=<4 ->
+ gsms_uart:at(Drv,"+CMGD="++integer_to_list(I)++","++integer_to_list(F)).
+
+drv_delete_all_message(Drv) ->
+ drv_delete_message(Drv,1,4).
+
+drv_read_message(Drv,I) when is_integer(I), I>=0 ->
+ case gsms_uart:at(Drv,"+CMGR="++integer_to_list(I)) of
+ ok ->
+ {error, no_such_index};
+ {ok,["+CMGR: "++_StatStoreLen,HexPdu]} ->
+ gsms_codec:decode_in_hex(HexPdu);
+ {error, Error} ->
+ {error, cms_error(Error)}
+ end.
+
+
+%% test send message
+drv_send_message(Drv,Opts,Body) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ lists:foreach(
+ fun(Pdu) ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ gsms_uart:atd(Drv,"+CMGS="++integer_to_list(Len),Hex)
+ end, PduList),
+ ok;
+ Error ->
+ Error
+ end.
+
+%% test write message
+drv_write_message(Drv,Opts,Body) ->
+ case gsms_codec:make_sms_submit(Opts, Body) of
+ {ok,PduList} ->
+ lists:foreach(
+ fun(Pdu) ->
+ gsms_codec:dump_yang(Pdu),
+ Bin = gsms_codec:encode_sms(Pdu),
+ Hex = gsms_codec:binary_to_hex(Bin),
+ Len = (length(Hex)-2) div 2,
+ gsms_uart:atd(Drv,"+CMGW="++integer_to_list(Len),Hex)
+ end, PduList),
+ ok;
+ Error ->
+ Error
+ end.
+
+parse_index_lists(Ts) ->
+ parse_index_lists(Ts, []).
+
+parse_index_lists([{'(',_}|Ts], Acc) -> parse_index_list(Ts, [], Acc);
+parse_index_lists([], Acc) -> {ok,lists:reverse(Acc)};
+parse_index_lists(_Ts, _Acc) -> {error, {syntax_error, _Ts}}.
+
+parse_index_lists1([{',',_}|Ts], Acc) -> parse_index_lists(Ts, Acc);
+parse_index_lists1([], Acc) -> {ok,lists:reverse(Acc)};
+parse_index_lists1(_Ts, _Acc) -> {error, {syntax_error, _Ts}}.
+
+%%
+%% ival = <i> '-' <j>
+%% ival = <i>
+%% ival-list = '(' (ival (',' ival)*) ? ')'
+%%
+parse_index_list([{integer,_,I},{'-',_},{integer,_,J}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[{I,J}|Iv],Acc);
+parse_index_list([{integer,_,I}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[I|Iv],Acc);
+parse_index_list([{')',_}|Ts],Iv,Acc) ->
+ parse_index_lists1(Ts,[lists:reverse(Iv)|Acc]);
+parse_index_list(_Ts, _Iv, _Acc) ->
+ {error, {syntax_error,_Ts}}.
+
+parse_index_list1([{',',_},{integer,_,I},{'-',_},{integer,_,J}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[{I,J}|Iv],Acc);
+parse_index_list1([{',',_},{integer,_,I}|Ts],Iv,Acc) ->
+ parse_index_list1(Ts,[I|Iv],Acc);
+parse_index_list1([{')',_}|Ts],Iv,Acc) ->
+ parse_index_lists1(Ts,[lists:reverse(Iv)|Acc]);
+parse_index_list1(_Ts,_Iv,_Acc) ->
+ {error, {syntax_error,_Ts}}.
+
+expand_index_list([{I,J}|Is]) when is_integer(I),is_integer(J),I>=0,I=<J ->
+ lists:seq(I,J) ++ expand_index_list(Is);
+expand_index_list([I|Is]) when is_integer(I), I>=0 ->
+ [I|expand_index_list(Is)];
+expand_index_list([]) ->
+ [].
+
+cms_error(Code) when is_list(Code) ->
+ Code ++ ": " ++ cms_error_string(list_to_integer(Code)).
+
+cms_error_string(300) -> "Phone failure";
+cms_error_string(301) -> "SMS service of phone reserved";
+cms_error_string(302) -> "Operation not allowed";
+cms_error_string(303) -> "Operation not supported";
+cms_error_string(304) -> "Invalid PDU mode parameter";
+cms_error_string(305) -> "Invalid text mode parameter";
+cms_error_string(310) -> "SIM not inserted";
+cms_error_string(311) -> "SIM PIN necessary";
+cms_error_string(312) -> "PH-SIM PIN necessary";
+cms_error_string(313) -> "SIM failure";
+cms_error_string(314) -> "SIM busy";
+cms_error_string(315) -> "SIM wrong";
+cms_error_string(320) -> "Memory failure";
+cms_error_string(321) -> "Invalid memory index";
+cms_error_string(322) -> "Memory full";
+cms_error_string(330) -> "SMSC (message service center) address unknown";
+cms_error_string(331) -> "No network service";
+cms_error_string(332) -> "Network timeout";
+cms_error_string(500) -> "Unknown error";
+cms_error_string(512) -> "Manufacturer specific";
+cms_error_string(_) -> "Unknown".
+
+
+setopts(Opts, State) ->
+ setopts(Opts, State, []).
+
+setopts([Opt|Opts], State, Opts1) ->
+ case Opt of
+ {simpin,Pin} when is_list(Pin) ->
+ setopts(Opts, State#state { simpin = Pin }, Opts1);
+ {bnumber,BNumber} when is_list(BNumber) ->
+ setopts(Opts, State#state { bnumber = BNumber }, Opts1);
+ {attributes,As} when is_list(As) ->
+ setopts(Opts, State#state { attributes = As }, Opts1);
+ {segment_timeout,T} when is_integer(T), T >= 0 ->
+ setopts(Opts, State#state { segment_timeout = T }, Opts1);
+ {send_delay, T} when is_integer(T), T >= 0 ->
+ setopts(Opts, State#state { send_delay = T }, Opts1);
+ {concat_8bit, B} when is_boolean(B) ->
+ setopts(Opts, State#state { concat_8bit = B }, Opts1);
+ {concat_seq, B} when is_boolean(B) ->
+ setopts(Opts, State#state { concat_seq = B }, Opts1);
+ _ ->
+ setopts(Opts, State, [Opt|Opts1])
+ end;
+setopts([], State, Opts1) ->
+ {lists:reverse(Opts1), State}.
+
+
+next_concat_ref(Opts, State) ->
+ case lists:keymember(ref,1,Opts) of
+ true -> %% ref is givent in the pdu
+ {State#state.concat_ref, Opts};
+ false ->
+ CRef0 = if State#state.concat_seq ->
+ State#state.concat_ref + 1;
+ true ->
+ random:unifrom(16#10000)-1
+ end,
+ CRef1 = if State#state.concat_8bit ->
+ CRef0 band 16#ff;
+ true ->
+ CRef0 band 16#ffff
+ end,
+ {CRef1, [{ref,CRef1} | Opts]}
+ end.
+
+%%
+%% Continue send segments or we are done ?
+%%
+sending([], State) ->
+ State#state { sending=false, outq = []};
+sending(OutQ, State) ->
+ erlang:send_after(State#state.send_delay, self(), send),
+ State#state { sending=true, outq = OutQ }.
+
+handle_sms(Sms, Ix, State) ->
+ %% check if this is Sms is part of an concatenated message
+ case lists:keyfind(concat, 1, Sms#gsms_deliver_pdu.udh) of
+ false -> %% singleton message, forward
+ forward_sms(Sms, [Ix], State);
+ {concat,_MRef,1,1} -> %% singleton message, forward !
+ forward_sms(Sms, [Ix], State);
+ {concat,MRef,N,I} when I > 0, I =< N ->
+ %% check if we already have segments stored
+ Key = {Sms#gsms_deliver_pdu.addr,MRef},
+ Q = State#state.inq,
+ Tmo = State#state.segment_timeout,
+ case lists:keytake(Key, 1, Q) of
+ false -> %% new enqueue
+ TRef = start_timer(Tmo,{cancel,Key}),
+ Segments = [{I,Ix,Sms}],
+ Q1 = [{Key,TRef,N,Segments} | Q],
+ State#state { inq = Q1 };
+ {value,{_,TRef,N,Segments},Q0} ->
+ case lists:keymember(I,1,Segments) of
+ false ->
+ stop_timer(TRef),
+ Segments1 = [{I,Ix,Sms}|Segments],
+ case length(Segments1) of
+ N ->
+ {Sms1,Ixs} = assemble_sms(Segments1),
+ State1 = State#state { inq=Q0},
+ forward_sms(Sms1,Ixs,State1);
+ _ ->
+ TRef1 = start_timer(Tmo,{cancel,Key}),
+ Q1 = [{Key,TRef1,N,Segments1} | Q0],
+ State#state { inq=Q1 }
+ end;
+ true ->
+ lager:warning("segment ~w already received!", [I]),
+ State
+ end
+ end;
+ Concat ->
+ lager:warning("bad concat udh element ~p", [Concat]),
+ State
+ end.
+
+%%
+%% Send sms to gsms_router & delete stored segments
+%%
+forward_sms(Sms, Ixs, State) ->
+ gsms_router:input_from(State#state.bnumber, Sms),
+ lists:foreach(
+ fun(Ix) ->
+ drv_delete_message(State#state.drv, Ix)
+ end, Ixs),
+ State.
+
+%% assemble sms segments into one message,
+%% assume all I 1..N are present (by pigeon hole principle)
+-spec assemble_sms([isegment_t()]) ->
+ {#gsms_deliver_pdu{}, [Ix::integer()]}.
+
+assemble_sms(Segments) ->
+ [{1,Ix,Sms} | Segments1] = lists:keysort(1, Segments),
+ Udh0 = lists:keydelete(concat, 1, Sms#gsms_deliver_pdu.udh),
+ Sms0 = Sms#gsms_deliver_pdu { udh=Udh0, udl=0, ud=[] },
+ assemble_(Segments1,
+ [Sms#gsms_deliver_pdu.ud], Sms#gsms_deliver_pdu.udl,
+ [Ix], Sms0).
+
+assemble_([{_,Ix,Sms}|Segments], Uds, Udl, Ixs, Sms0) ->
+ assemble_(Segments,
+ [Sms#gsms_deliver_pdu.ud|Uds], Sms#gsms_deliver_pdu.udl + Udl,
+ [Ix|Ixs], Sms0);
+assemble_([], Uds, Udl, Ixs, Sms0) ->
+ Ud = lists:append(lists:reverse(Uds)),
+ {Sms0#gsms_deliver_pdu { udl = Udl, ud = Ud }, lists:reverse(Ixs)}.
+
+
+start_timer(Time, Message) ->
+ erlang:start_timer(Time, self(), Message).
+
+stop_timer(undefined) ->
+ ok;
+stop_timer(Ref) ->
+ erlang:cancel_timer(Ref),
+ receive
+ {timeout, Ref, _} ->
+ ok
+ after 0 ->
+ ok
+ end.
diff --git a/deps/gsms/src/gsms_app.erl b/deps/gsms/src/gsms_app.erl
new file mode 100644
index 0000000..700561b
--- /dev/null
+++ b/deps/gsms/src/gsms_app.erl
@@ -0,0 +1,83 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2010, Tony Rogvall
+%%% @doc
+%%%
+%%% @end
+%%% Created : 24 May 2010 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(gsms_app).
+
+-behaviour(application).
+
+-include("log.hrl").
+
+%% Application callbacks
+-export([start/2,
+ start/0,
+ stop/1,
+ stop/0]).
+
+%%%===================================================================
+%%% Application callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @spec start(StartType, StartArgs) -> {ok, Pid} |
+%% {ok, Pid, State} |
+%% {error, Reason}
+%% StartType = normal | {takeover, Node} | {failover, Node}
+%% StartArgs = term()
+%% @doc
+%% This function is called whenever an application is started using
+%% application:start/[1,2], and should start the processes of the
+%% application. If the application is structured according to the OTP
+%% design principles as a supervision tree, this means starting the
+%% top supervisor of the tree.
+%%
+%% @end
+%%--------------------------------------------------------------------
+start(_StartType, _StartArgs) ->
+ Args = case application:get_env(gsms, arguments) of
+ undefined -> [];
+ {ok,As} -> As
+ end,
+ gsms_sup:start_link(Args).
+
+%% @private
+start() ->
+ application:start(uart),
+ application:start(gsms).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% This function is called whenever an application has stopped. It
+%% is intended to be the opposite of Module:start/2 and should do
+%% any necessary cleaning up. The return value is ignored.
+%%
+%% @spec stop(State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+stop(_State) ->
+ ?debug("gsms_app: stop/1.", []),
+ ok.
+
+%% @private
+stop() ->
+ application:stop(gsms).
diff --git a/deps/gsms/src/gsms_codec.erl b/deps/gsms/src/gsms_codec.erl
new file mode 100644
index 0000000..c904f77
--- /dev/null
+++ b/deps/gsms/src/gsms_codec.erl
@@ -0,0 +1,1067 @@
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% Send/Recive SMS with GSM modem
+%%% @end
+%%% Created : 15 Oct 2012 by Tony Rogvall <tony@rogvall.se>
+
+-module(gsms_codec).
+
+-compile(export_all).
+
+-export([decode_in/1, decode_in_hex/1]).
+-export([decode_out/1, decode_out_hex/1]).
+-export([hex_to_binary/1]).
+-export([is_valid_scts/1]).
+-export([decode_dcs/1]).
+
+-import(lists, [reverse/1]).
+
+-include("log.hrl").
+-include("../include/gsms.hrl").
+
+-define(Q, $").
+
+-define(decode_bool(X), ((X) =/= 0)).
+-define(encode_bool(X), if (X) -> 1; true -> 0 end).
+-define(is_byte(X), (((X) band (bnot 16#ff)) =:= 0)).
+-define(is_short(X), (((X) band (bnot 16#ffff)) =:= 0)).
+
+decode_in_hex(RawData) ->
+ decode_in(hex_to_binary(RawData)).
+
+decode_out_hex(RawData) ->
+ decode_out(hex_to_binary(RawData)).
+
+-spec decode_in(Message::binary()) ->
+ {ok, #gsms_deliver_pdu{}} |
+ {error,Reason::atom()}.
+
+decode_in(<<L1,SmscAddr:L1/binary,
+ TP_RP:1,TP_UDHI:1,TP_SRI:1,R1:1,R2:1,TP_MMS:1,?MTI_SMS_DELIVER:2,
+ AddrLen,Data0/binary>>) ->
+ ALen = (AddrLen + 1) bsr 1, %% number of bytes
+ <<AType,Addr:ALen/binary,TP_PID,TP_DCS,
+ TP_SCTS:7/binary,TP_UDL,TP_UD/binary>> = Data0,
+ AddrType = decode_addr_type(AType),
+ Dcs = decode_dcs(TP_DCS),
+ {UDH,UD} = decode_ud(Dcs,TP_UDHI,TP_UDL,TP_UD),
+ {ok, #gsms_deliver_pdu {
+ smsc = decode_smsc_addr(SmscAddr),
+ rp = ?decode_bool(TP_RP),
+ udhi = ?decode_bool(TP_UDHI),
+ sri = ?decode_bool(TP_SRI),
+ res1 = R1,
+ res2 = R2,
+ mms = ?decode_bool(TP_MMS),
+ addr = decode_addr(AddrType,Addr),
+ pid = TP_PID,
+ dcs = Dcs,
+ scts = decode_scts(TP_SCTS),
+ udl = TP_UDL,
+ udh = UDH,
+ ud = UD
+ }};
+decode_in(Binary) when is_binary(Binary) ->
+ {error, einval}.
+
+
+-spec decode_out(Message::binary()) ->
+ {ok, #gsms_submit_pdu{}} |
+ {error,Reason::atom()}.
+
+decode_out(<<L1,SmscAddr:L1/binary,
+ TP_RP:1,TP_UDHI:1,TP_SRR:1,TP_VPF:2,TP_RD:1,?MTI_SMS_SUBMIT:2,
+ TP_MREF,
+ AddrLen, %% in semi octets
+ Data0/binary>>) ->
+ ALen = (AddrLen + 1) bsr 1, %% number of bytes
+ <<AType,Addr:ALen/binary,TP_PID,TP_DCS,Data1/binary>> = Data0,
+ AddrType = decode_addr_type(AType),
+ VPF = decode_vpf(TP_VPF),
+ VPLen = case VPF of
+ none -> 0;
+ relative -> 1;
+ enhanced -> 7;
+ absolute -> 7
+ end,
+ <<VP:VPLen/binary, TP_UDL:8, TP_UD/binary>> = Data1,
+ Dcs = decode_dcs(TP_DCS),
+ {UDH,UD} = decode_ud(Dcs,TP_UDHI,TP_UDL,TP_UD),
+ {ok, #gsms_submit_pdu {
+ smsc = decode_smsc_addr(SmscAddr),
+ rp = ?decode_bool(TP_RP),
+ udhi = ?decode_bool(TP_UDHI),
+ srr = ?decode_bool(TP_SRR),
+ vpf = VPF,
+ rd = ?decode_bool(TP_RD),
+ mref = TP_MREF,
+ addr = decode_addr(AddrType,Addr),
+ pid = TP_PID,
+ dcs = Dcs,
+ vp = decode_vp(VPF,VP),
+ udl = TP_UDL,
+ udh = UDH,
+ ud = UD
+ }};
+decode_out(Binary) when is_binary(Binary) ->
+ {error, einval}.
+
+
+
+%% return a list of pdu's
+-spec make_sms_submit(Opts::[gsms_pdu_option()],
+ Message::[integer()]) ->
+ {ok,[#gsms_submit_pdu{}]} |
+ {error, term()}.
+
+make_sms_submit(Opts,Message) ->
+ case set_pdu_opts(Opts,#gsms_submit_pdu{}) of
+ {ok,Pdu} ->
+ Ref = proplists:get_value(ref, Opts, 1),
+ {Dcs,UDList} = encode_ud(Pdu#gsms_submit_pdu.dcs, Message,
+ Pdu#gsms_submit_pdu.udh, Ref),
+ {ok,lists:map(
+ fun({Udl,Ud,Udhi0}) ->
+ Udhi = Udhi0 or Pdu#gsms_submit_pdu.udhi,
+ Pdu#gsms_submit_pdu { dcs=Dcs, udl=Udl,
+ ud=Ud, udhi=Udhi }
+ end, UDList)};
+ Error ->
+ Error
+ end.
+
+
+-spec set_pdu_opts(Opts::[gsms_pdu_option()], Pdu::#gsms_submit_pdu{}) ->
+ {ok,Pdu::#gsms_submit_pdu{}} |
+ {error, badarg}.
+
+
+set_pdu_opts([{Key,Value}|Kvs], R=#gsms_submit_pdu{}) ->
+ case Key of
+ smsc when is_record(Value,gsms_addr) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { smsc=Value });
+ smsc when is_list(Value),hd(Value)=:=$+ ->
+ Addr = #gsms_addr { type=international, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { smsc=Addr });
+ smsc when is_list(Value) ->
+ Addr = #gsms_addr { type=unknown, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { smsc=Addr});
+ rp when is_boolean(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { rp=Value });
+ udhi when is_boolean(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { udhi=Value });
+ udh when is_list(Value), Value =/= [] ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { udhi=true, udh=Value });
+ udh when Value =:= [] ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { udhi=false, udh=Value });
+ srr when is_boolean(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { srr=Value });
+ mref when ?is_byte(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { mref=Value });
+ vpf when Value =:= none;
+ Value =:= relative;
+ Value =:= enhanced;
+ Value =:= absolute ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { vpf=Value });
+ vp ->
+ case Value of
+ none ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu
+ { vpf = none, vp=undefined });
+ {relative, Seconds} when is_integer(Seconds), Seconds>=0 ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu
+ { vpf = relative, vp=Seconds });
+ {absolute,DateTimeTz} ->
+ case is_valid_scts(DateTimeTz) of
+ true ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu
+ { vpf = absolute,
+ vp=DateTimeTz });
+ false ->
+ {error, badarg}
+ end;
+ {enhanced,_} ->
+ {error, not_supported} %% yet
+ end;
+ addr when is_record(Value,gsms_addr) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { addr=Value });
+ addr when is_list(Value),hd(Value)=:=$+ ->
+ Addr = #gsms_addr { type=international, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { addr=Addr});
+ addr when is_list(Value) ->
+ Addr = #gsms_addr { type=unknown, addr=Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { addr=Addr});
+ pid when ?is_byte(Value) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { pid=Value });
+ dcs when is_record(Value,gsms_dcs) ->
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Value });
+ dcs when is_integer(Value) ->
+ Dcs = decode_dcs(Value),
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs });
+ type -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { type = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ class -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { class = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ alphabet -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { alphabet = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ compression -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { compression = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ store -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { store = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ wait_type -> %% fixme test
+ Dcs = R#gsms_submit_pdu.dcs,
+ Dcs1 = Dcs#gsms_dcs { wait_type = Value },
+ set_pdu_opts(Kvs, R#gsms_submit_pdu { dcs=Dcs1 });
+ %% recognized options, but not for pdu
+ notify -> %% fixme test?
+ set_pdu_opts(Kvs, R);
+ ref -> %% fixme test?
+ set_pdu_opts(Kvs, R);
+ _ ->
+ lager:debug("set_pdu_opts: unknown pdu option ~p",
+ [{Key,Value}]),
+ {error, badarg}
+ end;
+set_pdu_opts([], R) ->
+ {ok,R}.
+
+is_valid_scts({{Date,{H,M,S}},Tz}) ->
+ try calendar:valid_date(Date) of
+ true ->
+ if is_integer(H), H >= 0, H =< 23,
+ is_integer(M), M >= 0, M =< 59,
+ is_integer(S), S >= 0, S =< 59,
+ is_float(Tz) ->
+ true;
+ true ->
+ false
+ end;
+ false -> false
+ catch
+ error:_ -> false
+ end;
+is_valid_scts(_) ->
+ false.
+
+
+
+
+encode_sms(R=#gsms_submit_pdu{}) ->
+ SmscAddr = encode_smsc_addr(R#gsms_submit_pdu.smsc),
+ TP_RP = ?encode_bool(R#gsms_submit_pdu.rp),
+ TP_UDHI = ?encode_bool(R#gsms_submit_pdu.udhi),
+ TP_SRR = ?encode_bool(R#gsms_submit_pdu.srr),
+ TP_VPF = encode_vpf(R#gsms_submit_pdu.vpf),
+ TP_RD = ?encode_bool(R#gsms_submit_pdu.rd),
+ TP_MREF = R#gsms_submit_pdu.mref,
+ {AType,Addr,AddrLen} = encode_addr(R#gsms_submit_pdu.addr),
+ TP_PID = R#gsms_submit_pdu.pid,
+ TP_DCS = encode_dcs(R#gsms_submit_pdu.dcs),
+ TP_VP = encode_vp(R#gsms_submit_pdu.vpf,R#gsms_submit_pdu.vp),
+ TP_UDL = R#gsms_submit_pdu.udl,
+ TP_UD = R#gsms_submit_pdu.ud,
+ ?debug("SmscAddr=~p,Addr=~p,TP_VP=~p,TP_UD=~p\n",
+ [SmscAddr,Addr,TP_VP,TP_UD]),
+ L1 = byte_size(SmscAddr),
+ <<L1,SmscAddr:L1/binary,
+ TP_RP:1,TP_UDHI:1,TP_SRR:1,TP_VPF:2,TP_RD:1,?MTI_SMS_SUBMIT:2,
+ TP_MREF,
+ AddrLen, %% in semi octets
+ AType,
+ Addr/binary,
+ TP_PID,
+ TP_DCS,
+ TP_VP/binary,
+ TP_UDL,
+ TP_UD/binary>>.
+
+
+binary_to_hex(Bin) ->
+ [ element(I+1,{$0,$1,$2,$3,$4,$5,$6,$7,$8,$9,
+ $A,$B,$C,$D,$E,$F}) || <<I:4>> <= Bin ].
+
+hex_to_binary(RawData) ->
+ << << H:4 >> || H <- hex_norm_nibbles(RawData) >>.
+
+hex_norm_nibbles([$\s|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([$\t|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([$\r|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([$\n|Cs]) -> hex_norm_nibbles(Cs);
+hex_norm_nibbles([C|Cs]) ->
+ if C >= $0, C =< $9 -> [C-$0 | hex_norm_nibbles(Cs)];
+ C >= $A, C =< $F -> [(C-$A)+10 | hex_norm_nibbles(Cs)];
+ C >= $a, C =< $f -> [(C-$A)+10 | hex_norm_nibbles(Cs)]
+ end;
+hex_norm_nibbles([]) ->
+ [].
+
+%%
+%% addresses
+%%
+
+decode_smsc_addr_type(<<AType,_/binary>>) ->
+ decode_addr_type(AType);
+decode_smsc_addr_type(<<>>) ->
+ undefined.
+
+decode_smsc_addr(<<AType,Ds/binary>>) ->
+ T = decode_addr_type(AType),
+ A = decode_addr_semi(T, Ds),
+ #gsms_addr { type=T, addr=A };
+decode_smsc_addr(<<>>) ->
+ undefined.
+
+decode_vpf(2#00) -> none;
+decode_vpf(2#10) -> relative;
+decode_vpf(2#01) -> enhanced;
+decode_vpf(2#11) -> absolute.
+
+encode_vpf(none) -> 2#00;
+encode_vpf(relative) -> 2#10;
+encode_vpf(enhanced) -> 2#01;
+encode_vpf(absolute) -> 2#11.
+
+%% validity period in seconds
+decode_vp(none,<<>>) -> 0;
+decode_vp(relative,<<VP>>) ->
+ if VP =< 143 -> (VP+1)*5*60; %% 1..12 hours (+ 5min)
+ VP =< 167 -> (12*60 + (VP-143)*30)*60; %% 12..24 hours (+ 30min)
+ VP =< 196 -> (VP-166)*24*60*60; %% 2 .. 30 days
+ true -> ((VP-192)*7*24)*60*60 %% 5 .. 63 weeks
+ end;
+decode_vp(absolute,V) ->
+ decode_scts(V).
+
+encode_vp(none, _) -> <<>>;
+encode_vp(relative, V) ->
+ Min_0 = V div 60, %% number of minutes
+ Hour_1 = Min_0 div 60, %% number of hours
+ _Min_1 = Min_0 rem 60, %% mintes with in the hour
+ Day_2 = Hour_1 div 24, %% days
+ _Hour_2 = Hour_1 rem 24, %% hour with in day
+ Week_3 = Day_2 div 7, %% weeks
+ _Day_3 = Day_2 rem 7, %% day within week
+ if Week_3 >= 5, Week_3 =< 64 ->
+ <<((Week_3-5)+197)>>;
+ Day_2 >= 2, Day_2 =< 30 ->
+ <<((Day_2-2)+168)>>;
+ Min_0 >= 12*60, Min_0 =< 24*60 ->
+ <<(((Min_0 - 12*60) div 30) + 143)>>;
+ Min_0 >= 5 ->
+ <<((Min_0 div 5)-1)>>;
+ true ->
+ <<0>>
+ end;
+encode_vp(absolute,V) ->
+ encode_scts(V).
+
+encode_smsc_addr(undefined) ->
+ <<>>;
+encode_smsc_addr(#gsms_addr {type=T,addr=A}) ->
+ AType = encode_addr_type(T),
+ Ds = encode_addr_semi(T, A),
+ <<AType,Ds/binary>>.
+
+%% Handle the ones in use only! (FIXME)
+decode_addr_type(2#10000001) -> unknown;
+decode_addr_type(2#10010001) -> international;
+decode_addr_type(2#10101000) -> national;
+decode_addr_type(T) -> T.
+
+encode_addr_type(unknown) -> 16#81; %% 129
+encode_addr_type(international) -> 16#91; %% 145
+encode_addr_type(national) -> 16#A8;
+encode_addr_type(T) -> T.
+
+decode_addr(T, <<>>) ->
+ #gsms_addr { type=T, addr=[] };
+decode_addr(T, Data) ->
+ A = decode_addr_semi(T, Data),
+ #gsms_addr { type=T, addr=A }.
+
+encode_addr(#gsms_addr { type=T, addr=A }) ->
+ Addr = encode_addr_semi(T, A),
+ AddrLen = length(A), %% number semi digits
+ {encode_addr_type(T),Addr,AddrLen}.
+
+decode_addr_semi(international, SemiOctets) ->
+ [$+|decode_semi_octets(SemiOctets)];
+decode_addr_semi(_, SemiOctets) ->
+ decode_semi_octets(SemiOctets).
+
+encode_addr_semi(international, [$+|Ds]) ->
+ encode_semi_octets(Ds);
+encode_addr_semi(_, Ds) ->
+ encode_semi_octets(Ds).
+
+
+decode_bcd(<<H:4,L:4, T/binary>>) ->
+ [H*10+L | decode_bcd(T)];
+decode_bcd(<<>>) ->
+ [].
+
+decode_swapped_bcd(Bin) ->
+ [(H*10+L) || <<L:4,H:4>> <= Bin].
+
+encode_swapped_bcd(Ds) ->
+ << <<(D rem 10):4, (D div 10):4>> || D <- Ds >>.
+
+decode_semi_octets(<<>>) -> [];
+decode_semi_octets(<<16#F:4,D:4>>) -> [D+$0];
+decode_semi_octets(<<L:4,H:4,Ds/binary>>) -> [H+$0,L+$0|decode_semi_octets(Ds)].
+
+encode_semi_octets([]) -> <<>>;
+encode_semi_octets([D]) -> <<16#F:4,(D-$0):4>>;
+encode_semi_octets([H,L|Ds]) ->
+ <<(L-$0):4,(H-$0):4, (encode_semi_octets(Ds))/binary>>.
+
+decode_scts(Bin) ->
+ [YY,Mon,Day,Hour,Min,Sec,Tz] = decode_swapped_bcd(Bin),
+ {Year0,_,_} = date(), %% Year0 is current year
+ YY0 = Year0 rem 100, %% YY0 curent year two digits
+ Century = Year0 - YY0, %% Current Century,
+ Year = if YY > YY0+1 ->
+ YY + (Century - 100);
+ true ->
+ YY + Century
+ end,
+ Date = {Year,Mon,Day},
+ Time = {Hour,Min,Sec},
+ Tz0 = (15*(Tz band 16#7f))/60,
+ TzH = if (Tz band 16#80) =/= 0 -> -Tz0; true -> Tz0 end,
+ {{Date,Time},TzH}.
+
+encode_scts({{{Year0,Mon,Day},{Hour,Min,Sec}},TzH}) ->
+ Year = Year0 - 2000,
+ Tz = if TzH < 0 ->
+ 16#80 bor (trunc((TzH * 60)/15) band 16#7f);
+ true ->
+ trunc((TzH * 60)/15) band 16#7f
+ end,
+ encode_swapped_bcd([Year,Mon,Day,Hour,Min,Sec,Tz]).
+
+
+decode_dcs(V) ->
+ if (V bsr 6) =:= 2#00 -> %% 00xxxxxx
+ #gsms_dcs { type=message,
+ compression = if V band 2#00100000 =:= 0 ->
+ uncompressed;
+ true -> compressed
+ end,
+ alphabet = case V band 2#1100 of
+ 2#0000 -> default;
+ 2#0100 -> octet;
+ 2#1000 -> ucs2;
+ 2#1100 -> reserved
+ end,
+ class = case V band 2#11 of
+ 2#00 -> alert;
+ 2#01 -> me;
+ 2#10 -> sim;
+ 2#11 -> te
+ end };
+ (V bsr 4) =:= 2#1100 -> %% message waiting, discard
+ dcs_message_waiting(V,discard,default);
+ (V bsr 4) =:= 2#1101 -> %% message waiting, store
+ dcs_message_waiting(V,store,default);
+ (V bsr 4) =:= 2#1110 -> %% message waiting, store
+ dcs_message_waiting(V,store,ucs2);
+ (V bsr 4) =:= 2#1111 ->
+ #gsms_dcs { type=data,compression=uncompressed,
+ alphabet=if V band 2#0100 =:= 0 -> octet;
+ true -> default
+ end,
+ class = case V band 2#11 of
+ 2#00 -> alert;
+ 2#01 -> me;
+ 2#10 -> sim;
+ 2#11 -> te
+ end
+ };
+ true ->
+ lager:error("unknown dcs value ~p", [V]),
+ #gsms_dcs {}
+ end.
+
+dcs_message_waiting(V,Store,Alphabet) ->
+ #gsms_dcs {type=message_waiting,
+ compression=uncompressed,
+ alphabet=Alphabet,
+ store=Store,
+ active=if V band 2#0100 =:= 0 ->
+ inactive;
+ true -> active
+ end,
+ wait_type = case V band 2#11 of
+ 2#00 -> voicemail;
+ 2#01 -> fax;
+ 2#10 -> email;
+ 2#11 -> other
+ end
+ }.
+
+
+encode_dcs(#gsms_dcs{type=message,compression=Comp,
+ alphabet=Alpha,class=Class}) ->
+ 2#00000000 +
+ if Comp =:= compressed -> 2#00100000;
+ true -> 2#00000000
+ end +
+ case Alpha of
+ default -> 2#0000;
+ octet -> 2#0100;
+ ucs2 -> 2#1000;
+ reserved -> 2#1100
+ end +
+ case Class of
+ alert -> 2#00;
+ me -> 2#01;
+ sim -> 2#10;
+ te -> 2#11
+ end;
+encode_dcs(#gsms_dcs {type=message_waiting,compression=uncompressed,
+ alphabet=Alphabet,store=Store,active=Active,
+ wait_type=WaitType}) ->
+ if Alphabet =:= ucs2, Store =:= store ->
+ 2#11110000;
+ Alphabet =:= default, Store =:= store ->
+ 2#11100000;
+ Alphabet =:= default, Store =:= discard ->
+ 2#11000000
+ end +
+ if Active =:= active -> 2#0100;
+ true -> 2#0000
+ end +
+ case WaitType of
+ voicemail -> 2#00;
+ fax -> 2#01;
+ email -> 2#10;
+ other -> 2#11
+ end;
+encode_dcs(#gsms_dcs{type=data,compression=uncompressed,
+ alphabet=Alphabet,class=Class}) ->
+ 2#11110000 +
+ if Alphabet =:= octet -> 2#100;
+ true -> 2#000
+ end +
+ case Class of
+ alert -> 2#00;
+ me -> 2#01;
+ sim -> 2#10;
+ te -> 2#11
+ end.
+
+
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=default},
+ 0, UDL, Data) ->
+ L7 = UDL, %% number of septets
+ <<Gsm8:L7/binary,_/binary>> = decode_gsm7(Data),
+ {[],gsms_0338:decode(Gsm8)};
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=default},
+ 1, UDL, Data = <<UDHL,_/binary>>) ->
+ %% UDHL as number of septets and including it self
+ UDHL7 = ((UDHL+1)*8 + 6) div 7,
+ L7 = UDL-UDHL7, %% number of septets in message
+ <<_:UDHL7/binary,Gsm8:L7/binary,_/binary>> = decode_gsm7(Data),
+ <<_,UDHBin:UDHL/binary,_/binary>> = Data,
+ UDH = decode_udh(UDHBin),
+ {UDH,gsms_0338:decode(Gsm8)};
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=ucs2},
+ 0,UDL,Data1) ->
+ case Data1 of
+ <<Data2:UDL/binary, _/binary>> ->
+ Ucs = [ U || <<U:16/big>> <= Data2 ],
+ {[],Ucs}
+ end;
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=ucs2},
+ 1, UDL, Data1 = <<UDHL,UDHBin:UDHL/binary,_/binary>>) ->
+ Pad = (UDHL+1) band 1,
+ UDL8 = UDL - (UDHL+1+Pad),
+ %% io:format("UDL=~w, Pad=~w, UDL8=~w\n", [UDL,Pad,UDL8]),
+ case Data1 of
+ <<_,_:UDHL/binary,_:Pad/unit:8,Data2:UDL8/binary>> ->
+ UDH = decode_udh(UDHBin),
+ {UDH, [ U || <<U:16/big>> <= Data2 ]}
+ end;
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=octet},
+ 0,UDL, Data1) ->
+ case Data1 of
+ <<Data2:UDL/binary>> ->
+ {[], binary_to_list(Data2)}
+ end;
+decode_ud(#gsms_dcs{compression=uncompressed,alphabet=octet},
+ 1, UDL, <<UDHL,UDHBin:UDHL/binary,Data1/binary>>) ->
+ UDL8 = UDL - (UDHL+1),
+ case Data1 of
+ <<_,_:UDHL/binary,Data2:UDL8/binary>> ->
+ UDH = decode_udh(UDHBin),
+ {UDH, binary_to_list(Data2)}
+ end.
+
+
+
+encode_ud(Dcs0=#gsms_dcs{alphabet=Alpha0},
+ Message, UDH, Ref) ->
+ %% fixme: check that there are no concat header ?
+ %% calculate total number of letters in message and the encoding
+ {Alpha,_TotLen} = message_len(Message,Alpha0),
+ Dcs = Dcs0#gsms_dcs{alphabet=Alpha},
+ case encode_ud_(Dcs, Message, UDH) of
+ {UDL,UD,[],UDHI} ->
+ {Dcs, [{UDL,iolist_to_binary(UD),UDHI}]};
+ {_UDL,_UD,_Message1,_} ->
+ {Dcs,encode_ud_loop(Dcs, Message, UDH, Ref, 1, [])}
+ end.
+
+encode_ud_loop(Dcs, Message, UDH, Ref, I, Acc) ->
+ case encode_ud_(Dcs, Message, [{concat,Ref,0,I}|UDH]) of
+ {UDL,UD,[],UDHI} ->
+ ud_patch([{UDL,UD,UDHI}|Acc], I, []);
+ {UDL,UD,Message1,UDHI} ->
+ UDH1 = [], %% kill header for segments
+ encode_ud_loop(Dcs,Message1,UDH1,Ref,I+1,[{UDL,UD,UDHI} | Acc])
+ end.
+
+%% patch concat and reverse list
+ud_patch([{UDL,UD,UDHI}|Tail], N, Acc) ->
+ case UD of
+ [UDHL,[<<?IE_CONCAT8,3,Ref:8,_N,I>>|UDH],Data] ->
+ UD1 = iolist_to_binary([UDHL,[<<?IE_CONCAT8,3,Ref:8,N,I>>|UDH],
+ Data]),
+ ud_patch(Tail, N, [{UDL,UD1,UDHI}|Acc]);
+ [UDHL,[<<?IE_CONCAT16,4,Ref:16,_N,I>>|UDH],Data] ->
+ UD1 = iolist_to_binary([UDHL,[<<?IE_CONCAT16,4,Ref:16,N,I>>|UDH],
+ Data]),
+ ud_patch(Tail, N, [{UDL,UD1,UDHI}|Acc])
+ end;
+ud_patch([], _N, Acc) ->
+ Acc.
+
+
+encode_ud_(#gsms_dcs{compression=uncompressed,alphabet=default},
+ Message, UDH) ->
+ case len_udh(UDH) of
+ 0 ->
+ {Gsm8,Message1} = gsms_0338:encode(Message,?MAX_7BIT_LEN),
+ L7 = byte_size(Gsm8), %% number of septets
+ L8 = (L7*7 + 7) div 8, %% number of octets needed
+ <<Gsm7:L8/binary,_/binary>> = encode_gsm7(Gsm8),
+ {L7,Gsm7,Message1,false};
+ UDHL ->
+ %% UDHL as number of septets and including it self
+ UDHL8 = (UDHL+1)*8, %% number of bits including self
+ UDHL7 = (UDHL8 + 6) div 7, %% number of septets
+ UDHData = encode_udh(UDH),
+ Message0 = prepend(UDHL7,0,Message),
+ {Gsm8,Message1} = gsms_0338:encode(Message0,?MAX_7BIT_LEN-UDHL7),
+ Pad = UDHL7*7 - UDHL8,
+ L7 = byte_size(Gsm8)-UDHL7, %% number of septets in message
+ L8 = (L7*7+Pad+7) div 8, %% number of octets needed
+ <<_,_:UDHL/binary,Gsm7:L8/binary,_/binary>> = encode_gsm7(Gsm8),
+ {UDHL7+L7,[UDHL,UDHData,Gsm7],Message1,true}
+ end;
+encode_ud_(#gsms_dcs{compression=uncompressed,alphabet=ucs2}, Message, UDH) ->
+ case len_udh(UDH) of
+ 0 ->
+ {Ucs2,Message1} = encode_ucs2(Message,?MAX_16BIT_LEN,[]),
+ N = byte_size(Ucs2),
+ {N, Ucs2, Message1, false};
+ UDHL ->
+ UDHData = encode_udh(UDH),
+ Pad = (UDHL+1) band 1,
+ UDHL16 = (UDHL+1+Pad) div 2, %% check this
+ {Ucs2,Message1} = encode_ucs2(Message,?MAX_16BIT_LEN-UDHL16,[]),
+ N = byte_size(Ucs2),
+ Ucs21 = prepend(Pad,0,Ucs2),
+ UDL = N + UDHL + 1 + Pad,
+ {UDL,[UDHL,UDHData,Ucs21],Message1,true}
+ end;
+encode_ud_(#gsms_dcs{compression=uncompressed,alphabet=octet}, Message, UDH) ->
+ case len_udh(UDH) of
+ 0 ->
+ {Octets,Message1} = encode_octets(Message,?MAX_8BIT_LEN,[]),
+ N = byte_size(Octets),
+ {N, Octets, Message1, false};
+ UDHL ->
+ UDHData = encode_udh(UDH),
+ {Octets,Message1} = encode_octets(Message,?MAX_8BIT_LEN-UDHL,[]),
+ N = byte_size(Octets),
+ {UDHL+N,[UDHL,UDHData,Octets],Message1,true}
+ end.
+
+
+
+encode_octets(Cs, 0, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), Cs};
+encode_octets([C|Cs], I, Acc) ->
+ encode_octets(Cs, I-1, [C|Acc]);
+encode_octets([], _I, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), []}.
+
+%%
+%% Fixme: add BOM (byte order mark) character
+%% BOM = 0xFEFF, if BOM is received as 0xFEFF then
+%% it is coded in default big endian, else it
+%% will be read as 0xFFFE and is coded in little endian.
+%%
+encode_ucs2(Cs, 0, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), Cs};
+encode_ucs2([C|Cs], I, Acc) ->
+ encode_ucs2(Cs, I-1, [<<C:16/big>>|Acc]);
+encode_ucs2([], _I, Acc) ->
+ {iolist_to_binary(lists:reverse(Acc)), []}.
+
+
+
+message_len(Cs) ->
+ message_len_auto_(Cs,0).
+
+message_len(Cs,auto) ->
+ message_len_auto_(Cs,0);
+message_len(Cs,default) ->
+ {default,message_len_default_(Cs,0)};
+message_len(Cs,octet) ->
+ {octet,message_len_octet_(Cs,0)};
+message_len(Cs,ucs2) ->
+ {ucs2,message_len_ucs2_(Cs,0)}.
+
+
+message_len_auto_(Cs,N) ->
+ try message_len_default_(Cs,N) of
+ Len7 -> {default,Len7}
+ catch
+ error:_ ->
+ {ucs2,message_len_ucs2_(Cs,N)}
+ end.
+
+message_len_default_([C|Cs], Len) ->
+ case gsms_0338:encode_char(C) of
+ [_Esc,_Y] -> message_len_default_(Cs, Len+2);
+ _Y -> message_len_default_(Cs, Len+1)
+ end;
+message_len_default_([], Len) ->
+ Len.
+
+message_len_octet_([C|Cs], Len) when ?is_byte(C) ->
+ message_len_octet_(Cs, Len+1);
+message_len_octet_([], Len) ->
+ Len.
+
+message_len_ucs2_([C|Cs], Len) when ?is_short(C) ->
+ message_len_ucs2_(Cs, Len+1);
+message_len_ucs2_([], Len) ->
+ Len.
+
+
+%%
+%% Calculate number of bytes in UDH (not incuding the length byte)
+%%
+-spec len_udh(IEs::[gsms_ie()]) -> integer().
+
+len_udh([]) ->
+ 0;
+len_udh(IEs) ->
+ len_udh(IEs, 0).
+
+len_udh([{concat,Ref,_N,_I}|IEs],Len) ->
+ if Ref >= 0, Ref =< 16#ff -> len_udh(IEs, Len+5);
+ Ref >= 0, Ref =< 16#ffff -> len_udh(IEs, Len+6)
+ end;
+len_udh([{concat8,_Ref,_N,_I}|IEs], Len) -> len_udh(IEs,Len+5);
+len_udh([{concat16,_Ref,_N,_I}|IEs], Len) -> len_udh(IEs,Len+6);
+len_udh([{port,Dst,Src}|IEs], Len) ->
+ if Dst >= 0, Dst =< 16#ff,
+ Src >= 0, Src =< 16#ff ->
+ len_udh(IEs,Len+4);
+ Dst >= 0, Dst =< 16#ffff,
+ Src >= 0, Src =< 16#ffff ->
+ len_udh(IEs,Len+6)
+ end;
+len_udh([{port8,_Dst,_Src}|IEs], Len) -> len_udh(IEs,Len+4);
+len_udh([{port16,_Dst,_Src}|IEs],Len) -> len_udh(IEs,Len+6);
+len_udh([], Len) -> Len.
+
+%%
+%% Encode user data header IEs
+%%
+-spec encode_udh(IEs::[gsms_ie()]) -> [binary()].
+
+encode_udh([]) ->
+ [];
+encode_udh(IEs) ->
+ encode_udh(IEs, []).
+
+encode_udh([{concat,Ref,N,I}|IEs], Acc) ->
+ if Ref >= 0, Ref =< 16#ff ->
+ encode_udh(IEs,[ <<?IE_CONCAT8,3,Ref,N,I>>|Acc]);
+ Ref >= 0, Ref =< 16#ffff ->
+ encode_udh(IEs,[ <<?IE_CONCAT16,4,Ref:16,N,I>> | Acc ])
+ end;
+encode_udh([{concat8,Ref,N,I}|IEs], Acc) ->
+ encode_udh(IEs,[ <<?IE_CONCAT8,3,Ref,N,I>>|Acc]);
+encode_udh([{concat16,Ref,N,I}|IEs], Acc) ->
+ encode_udh(IEs,[ <<?IE_CONCAT16,4,Ref:16,N,I>> | Acc ]);
+encode_udh([{port,Dst,Src}|IEs], Acc) ->
+ if Dst >= 0, Dst =< 16#ff,
+ Src >= 0, Src =< 16#ff ->
+ encode_udh(IEs,[ <<?IE_PORT8,2,Dst,Src>> | Acc]);
+ Dst >= 0, Dst =< 16#ffff,
+ Src >= 0, Src =< 16#ffff ->
+ encode_udh(IEs,[<<?IE_PORT16,4,Dst:16,Src:16>> | Acc])
+ end;
+encode_udh([{port8,DstPort,SrcPort}|IEs], Acc) ->
+ encode_udh(IEs,[ <<?IE_PORT8,2,DstPort,SrcPort>> | Acc]);
+encode_udh([{port16,DstPort,SrcPort}|IEs],Acc) ->
+ encode_udh(IEs,[<<?IE_PORT16,4,DstPort:16,SrcPort:16>> | Acc]);
+encode_udh([{ie,IE,Args}|IEs], Acc) when is_binary(Args) ->
+ encode_udh(IEs,[<<IE,(byte_size(Args)),Args/binary>> | Acc]);
+encode_udh([], Acc) ->
+ reverse(Acc).
+
+
+decode_udh(UDHBin) when is_binary(UDHBin) ->
+ decode_udh(UDHBin, []).
+
+decode_udh(<<?IE_CONCAT8,3,Ref,N,I,IEs/binary>>, Acc) ->
+ decode_udh(IEs,[{concat,Ref,N,I}|Acc]);
+decode_udh(<<?IE_CONCAT16,4,Ref:16,N,I,IEs/binary>>,Acc) ->
+ decode_udh(IEs,[{concat,Ref,N,I}|Acc ]);
+decode_udh(<<?IE_PORT8,2,DstPort,SrcPort,IEs/binary>>, Acc) ->
+ decode_udh(IEs,[{port,DstPort,SrcPort}| Acc]);
+decode_udh(<<?IE_PORT16,4,DstPort:16,SrcPort:16,IEs/binary>>,Acc) ->
+ decode_udh(IEs,[{port,DstPort,SrcPort} | Acc]);
+decode_udh(<<IE,N,Args:N/binary,IEs/binary>>,Acc) ->
+ decode_udh(IEs,[{ie,IE,Args}|Acc]);
+decode_udh(<<>>, Acc) ->
+ reverse(Acc).
+
+
+decode_gsm7(<<X1:1,Y7:7, X2:2,Y6:6, X3:3,Y5:5, X4:4,Y4:4,
+ X5:5,Y3:3, X6:6,Y2:2, X7:7,Y1:1, More/binary>>) ->
+ <<0:1,Y7:7,
+ 0:1,Y6:6,X1:1,
+ 0:1,Y5:5,X2:2,
+ 0:1,Y4:4,X3:3,
+ 0:1,Y3:3,X4:4,
+ 0:1,Y2:2,X5:5,
+ 0:1,Y1:1,X6:6,
+ 0:1,X7:7,
+ (decode_gsm7(More))/binary>>;
+decode_gsm7(<<>>) ->
+ <<>>;
+decode_gsm7(More) when byte_size(More) > 0,
+ byte_size(More) < 7 ->
+ Pad = 7 - byte_size(More), %% 6..1
+ decode_gsm7(<<More/binary, 0:Pad/unit:8>>).
+
+
+
+%% encode gsm7 bit encoding
+encode_gsm7(<<0:1,Y7:7, 0:1,Y6:6,X1:1, 0:1,Y5:5,X2:2, 0:1,Y4:4,X3:3,
+ 0:1,Y3:3,X4:4, 0:1,Y2:2,X5:5, 0:1,Y1:1,X6:6, 0:1, X7:7,
+ More/binary >>) ->
+ <<X1:1,Y7:7, X2:2,Y6:6, X3:3,Y5:5, X4:4,Y4:4,
+ X5:5,Y3:3, X6:6,Y2:2, X7:7,Y1:1,
+ (encode_gsm7(More))/binary >>;
+encode_gsm7(<<>>) ->
+ <<0>>; %% extra bits to compensate for UDH padding.
+encode_gsm7(More) when byte_size(More) > 0,
+ byte_size(More) < 8 ->
+ Pad = 8 - byte_size(More), %% 7..1
+ encode_gsm7(<<More/binary, 0:Pad/unit:8>>).
+
+%% prepend N number of E's to a list
+prepend(N,E,List) when is_list(List) ->
+ prepend_list(N,E,List);
+prepend(N,E,Binary) when is_binary(Binary) ->
+ <<E:N/unit:8, Binary/binary>>.
+
+prepend_list(0,_,Acc) -> Acc;
+prepend_list(I,E,Acc) -> prepend_list(I-1,E,[E|Acc]).
+
+
+
+is_gsms_record(P) ->
+ case P of
+ #gsms_addr{} -> true;
+ #gsms_submit_pdu{} -> true;
+ #gsms_deliver_pdu{} -> true;
+ _ -> false
+ end.
+
+separator(D,S) ->
+ case is_gsms_record(D) of
+ true -> "";
+ false -> S
+ end.
+
+indent(I) ->
+ lists:duplicate(I, $\s).
+
+dump(P) ->
+ dump_json(P).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Format values and records in JSON format
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dump_json(P) ->
+ io:format("~s\n", [fmt_json(P)]).
+
+fmt_json(P) ->
+ case is_gsms_record(P) of
+ true -> fmt_json_record(0,P,"\n");
+ false -> fmt_json_value(0,P,"\n")
+ end.
+
+fmt_json_record(I,P=#gsms_addr{},N) ->
+ fmt_json_record(I,P,record_info(fields,gsms_addr),N);
+fmt_json_record(I,P=#gsms_submit_pdu{},N) ->
+ fmt_json_record(I,P,record_info(fields,gsms_submit_pdu),N);
+fmt_json_record(I,P=#gsms_deliver_pdu{},N) ->
+ fmt_json_record(I,P,record_info(fields,gsms_deliver_pdu),N).
+
+fmt_json_record(I,P,Fs,N) ->
+ [R|Ds] = tuple_to_list(P),
+ [ "{",N,
+ fmt_json_fields(I+2, R, [struct|Fs], [R|Ds], N),
+ N, indent(I), "}" ].
+
+fmt_json_fields(I,R,[F],[D],N) ->
+ [ [indent(I),fmt_json_field(I,R,F,D,N) ] ];
+fmt_json_fields(I,R,[F|Fs],[D|Ds],N) ->
+ [ [indent(I),fmt_json_field(I,R,F,D,N),",",N] |
+ fmt_json_fields(I,R,Fs,Ds,N)];
+fmt_json_fields(_I,_R,[],[],_N) ->
+ [].
+
+fmt_json_field(I,R,F,D,N) ->
+ Fk = atom_to_list(F),
+ Dk = fmt_json_value(I,R,F,D,N),
+ [Fk,": ",Dk].
+
+fmt_json_value(I,D,N) ->
+ fmt_json_value(I,undefined,undefined,D,N).
+
+fmt_json_value(I,R,F,D,N) ->
+ if
+ is_boolean(D) -> [atom_to_list(D)];
+ is_integer(D) -> [integer_to_list(D)];
+ is_atom(D) -> [?Q,atom_to_list(D),?Q];
+ is_binary(D), R =:= ipv4, (F =:= src orelse F =:= dst) ->
+ <<X1,X2,X3,X4>> = D,
+ [?Q,io_lib:format("~w.~w.~w.~w", [X1,X2,X3,X4]),?Q];
+ is_binary(D) ->
+ io_lib:format("~p", [D]);
+ is_tuple(D) ->
+ case is_gsms_record(D) of
+ true ->
+ fmt_json_record(I+2,D,N);
+ false ->
+ io_lib:format("~p", [D])
+ end;
+ is_list(D) ->
+ try iolist_size(D) of
+ _Sz -> [?Q,D,?Q]
+ catch
+ error:_ -> %% formt as JSON array?
+ fmt_json_array(D)
+ end
+ end.
+
+fmt_json_array(Ds) ->
+ ["[", fmt_json_elems(Ds), "]"].
+
+fmt_json_elems([D]) ->
+ fmt_json_value(0,D,"");
+fmt_json_elems([D|Ds]) ->
+ [fmt_json_value(0,D,""),"," | fmt_json_elems(Ds)];
+fmt_json_elems([]) -> [].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% YANG FORMAT
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dump_yang(P) ->
+ io:format("~s\n", [fmt_yang(P)]).
+
+fmt_yang(P) ->
+ case is_gsms_record(P) of
+ true -> fmt_yang_record(0,P,"\n");
+ false -> fmt_yang_value(0,P,"\n")
+ end.
+
+fmt_yang_record(I,P=#gsms_addr{},N) ->
+ fmt_yang_record(I,P,record_info(fields,gsms_addr),N);
+fmt_yang_record(I,P=#gsms_submit_pdu{},N) ->
+ fmt_yang_record(I,P,record_info(fields,gsms_submit_pdu),N);
+fmt_yang_record(I,P=#gsms_deliver_pdu{},N) ->
+ fmt_yang_record(I,P,record_info(fields,gsms_deliver_pdu),N).
+
+fmt_yang_record(I,P,Fs,N) ->
+ [R|Ds] = tuple_to_list(P),
+ [ atom_to_list(R), " {", N,
+ fmt_yang_fields(I+2, R, Fs, Ds, N),
+ indent(I), "}" ].
+
+fmt_yang_fields(I,R,[F|Fs],[D|Ds],N) ->
+ [ [indent(I),fmt_yang_field(I,R,F,D,N),separator(D,";"),N] |
+ fmt_yang_fields(I,R,Fs,Ds,N)];
+fmt_yang_fields(_I,_R,[],[],_N) ->
+ [].
+
+fmt_yang_field(I,R,F,D,N) ->
+ Fk = atom_to_list(F),
+ Dk = fmt_yang_value(I,R,F,D,N),
+ [Fk," ",Dk].
+
+fmt_yang_value(I,D,N) ->
+ fmt_yang_value(I,undefined,undefined,D,N).
+
+fmt_yang_value(I,R,F,D,N) ->
+ if
+ is_boolean(D) -> [atom_to_list(D)];
+ is_integer(D) -> [integer_to_list(D)];
+ is_atom(D) -> [atom_to_list(D)];
+ is_binary(D), R =:= ipv4, (F =:= src orelse F =:= dst) ->
+ <<X1,X2,X3,X4>> = D,
+ [?Q,io_lib:format("~w.~w.~w.~w", [X1,X2,X3,X4]),?Q];
+ is_binary(D) -> %% fixme!
+ [?Q,io_lib:format("~p", [D]),?Q];
+ is_tuple(D) ->
+ case is_gsms_record(D) of
+ true ->
+ fmt_yang_record(I+2,D,N);
+ false ->
+ io_lib:format("~p", [D])
+ end;
+ is_list(D) ->
+ try iolist_size(D) of
+ _Sz -> [?Q,D,?Q]
+ catch
+ error:_ -> %% formt as YANG array?
+ fmt_yang_array(D)
+ end
+ end.
+
+fmt_yang_array(Ds) ->
+ fmt_yang_elems(Ds).
+
+fmt_yang_elems([D]) ->
+ fmt_yang_value(0,D,"");
+fmt_yang_elems([D|Ds]) ->
+ [fmt_yang_value(0,D,"")," " | fmt_yang_elems(Ds)];
+fmt_yang_elems([]) -> [].
diff --git a/deps/gsms/src/gsms_if_sup.erl b/deps/gsms/src/gsms_if_sup.erl
new file mode 100644
index 0000000..3916a96
--- /dev/null
+++ b/deps/gsms/src/gsms_if_sup.erl
@@ -0,0 +1,78 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2013, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Malotte W Lönne <malotte@malotte.net>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%% sms interface supervisor
+%%%
+%%% Created: 2013 by Malotte W Lönne
+%%% @end
+
+-module(gsms_if_sup).
+
+-behaviour(supervisor).
+
+-include("log.hrl").
+%% external exports
+-export([start_link/0,
+ start_link/1,
+ stop/1]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link(Args) ->
+ ?info("~p: start_link: args = ~p\n", [?MODULE, Args]),
+ case supervisor:start_link({local, ?MODULE}, ?MODULE, Args) of
+ {ok, Pid} ->
+ {ok, Pid, {normal, Args}};
+ Error ->
+ Error
+ end.
+
+start_link() ->
+ supervisor:start_link({local,?MODULE}, ?MODULE, []).
+
+stop(_StartArgs) ->
+ ok.
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from supervisor
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%%----------------------------------------------------------------------
+init(Args) ->
+ ?info("~p: init: args = ~p,\n pid = ~p\n", [?MODULE, Args, self()]),
+ Interfaces =
+ lists:foldr(
+ fun({Mod,I,Opts},Acc) ->
+ Spec={{Mod,I},
+ {Mod, start_link, [I,Opts]},
+ permanent, 5000, worker, [Mod]},
+ [Spec | Acc]
+ end, [],
+ case application:get_env(gsms, interfaces) of
+ undefined -> [];
+ {ok,IfList} when is_list(IfList) ->
+ IfList
+ end),
+ ?info("~p: init: starting interfaces ~p", [?MODULE, Interfaces]),
+ {ok,{{one_for_one,3,5}, Interfaces}}.
diff --git a/deps/gsms/src/gsms_router.erl b/deps/gsms/src/gsms_router.erl
new file mode 100644
index 0000000..bb027f8
--- /dev/null
+++ b/deps/gsms/src/gsms_router.erl
@@ -0,0 +1,464 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Message router for SMS
+%%%
+%%% Created : 17 Apr 2013 by Tony Rogvall
+%%% @end
+%%%-------------------------------------------------------------------
+-module(gsms_router).
+
+-behaviour(gen_server).
+
+-include("log.hrl").
+-include("../include/gsms.hrl").
+
+%% API
+-export([start_link/1,
+ send/2,
+ subscribe/1,
+ unsubscribe/1,
+ join/2,
+ input_from/2]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+%% testing
+-export([dump/0]).
+
+-define(SERVER, ?MODULE).
+
+-record(subscription,
+ {
+ pid :: pid(), %% subscriber process
+ ref :: reference(), %% reference / monitor
+ filter = [] :: filter()
+ }).
+
+-record(interface,
+ {
+ pid :: pid(), %% interface pid
+ mon :: reference(), %% monitor reference
+ bnumber :: gsms_addr(), %% modem msisdn
+ rssi = 99 :: integer(), %% last known rssi value
+ attributes = [] :: [{atom(),term()}] %% general match keys
+ }).
+
+-record(state,
+ {
+ csq_ival = 0,
+ csq_tmr,
+ subs = [] :: [#subscription{}],
+ ifs = [] :: [#interface{}]
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec send(Options::list({Key::atom(), Value::term()}), Body::string()) ->
+ {ok, Ref::reference()} |
+ {error, Reason::term()}.
+
+send(Opts, Body) ->
+ gen_server:call(?SERVER, {send, Opts, Body}).
+
+-spec subscribe(Filter::[filter()]) -> {ok,Ref::reference()} |
+ {error,Reason::term()}.
+
+subscribe(Filter) ->
+ gen_server:call(?SERVER, {subscribe, self(), Filter}).
+
+-spec unsubscribe(Ref::reference()) -> ok.
+
+unsubscribe(Ref) ->
+ gen_server:call(?SERVER, {unsubscribe, Ref}).
+
+join(BNumber,Attributes) ->
+ gen_server:call(?SERVER, {join,self(),BNumber,Attributes}).
+
+%%
+%% Called from gsms_0705 backend to enter incoming message
+%%
+input_from(BNumber, Sms) ->
+ lager:debug("message input modem:~s, message = ~p\n",
+ [BNumber, Sms]),
+ ?SERVER ! {input_from, BNumber, Sms},
+ ok.
+
+%% Test functions
+%% @private
+dump() ->
+ gen_server:call(?SERVER, dump).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(Args) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, Args, []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init(Args) ->
+ Csq_ival = proplists:get_value(csq_ival, Args, 0),
+ Csq_tmr = if is_integer(Csq_ival), Csq_ival > 0 ->
+ erlang:start_timer(Csq_ival, self(), csq);
+ true -> undefined
+ end,
+ process_flag(trap_exit, true),
+ {ok, #state{ csq_ival=Csq_ival, csq_tmr=Csq_tmr}}.
+
+%%--------------------------------------------------------------------
+%% @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({send,Opts,Body}, _From, State) ->
+ %% forward to the correct interface handler
+ %% FIXME: add code to match attributes!
+ case proplists:get_value(bnumber, Opts) of
+ undefined ->
+ case State#state.ifs of
+ [I|_] ->
+ Reply = gsms_0705:send(I#interface.pid, Opts, Body),
+ {reply, Reply, State};
+ [] ->
+ {reply, {error,enoent}, State}
+ end;
+ BNumber ->
+ case lists:keyfind(BNumber,#interface.bnumber,State#state.ifs) of
+ false ->
+ {reply, {error,enoent}, State};
+ I ->
+ Reply = gsms_0705:send(I#interface.pid, Opts, Body),
+ {reply, Reply, State}
+ end
+ end;
+handle_call({subscribe,Pid,Filter}, _From, State) ->
+ Ref = erlang:monitor(process, Pid),
+ Subs = [#subscription { pid = Pid,
+ ref = Ref,
+ filter = Filter } | State#state.subs],
+ {reply, {ok,Ref}, State#state { subs = Subs} };
+handle_call({unsubscribe,Ref}, _From, State) ->
+ case lists:keytake(Ref, #subscription.ref, State#state.subs) of
+ false ->
+ {reply, ok, State};
+ {value,_S,Subs} ->
+ erlang:demonitor(Ref, [flush]),
+ {reply, ok, State#state { subs = Subs} }
+ end;
+handle_call({join,Pid,BNumber,Attributes}, _From, State) ->
+ case lists:keytake(BNumber, #interface.bnumber, State#state.ifs) of
+ false ->
+ ?debug("gsms_router: process ~p, bnumber ~p joined.",
+ [Pid, BNumber]),
+ State1 = add_interface(Pid,BNumber,Attributes,State),
+ {reply, ok, State1};
+ {value,I,IFs} ->
+ receive
+ {'EXIT', OldPid, _Reason} when I#interface.pid =:= OldPid ->
+ ?debug("join: restart detected", []),
+ State1 = add_interface(Pid,BNumber,Attributes,
+ State#state { ifs=IFs} ),
+ {reply, ok, State1}
+ after 0 ->
+ {reply, {error,ealready}, State}
+ end
+ end;
+handle_call(dump, _From, State=#state {subs = Subs, ifs= Ifs}) ->
+ io:format("LoopData:\n", []),
+ io:format("Subscriptions:\n", []),
+ lists:foreach(fun(_Sub=#subscription {pid = Pid, ref = Ref, filter = F}) ->
+ io:format("pid ~p, ref ~p, filter ~p~n",
+ [Pid, Ref, F])
+ end,
+ Subs),
+ io:format("Interfaces:\n", []),
+ lists:foreach(fun(_If=#interface {pid = Pid, mon = Ref,
+ bnumber = B, attributes = A}) ->
+ io:format("pid ~p, ref ~p, bnumber ~p, attributes ~p~n",
+ [Pid, Ref, B, A])
+ end,
+ Ifs),
+ {reply, ok, State};
+
+handle_call(_Request, _From, State) ->
+ 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) ->
+ {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({'DOWN',Ref,process,Pid,_Reason}, State) ->
+ case lists:keytake(Ref, #subscription.ref, State#state.subs) of
+ false ->
+ case lists:keytake(Pid, #interface.pid, State#state.ifs) of
+ false ->
+ {noreply, State};
+ {value,_If,Ifs} ->
+ ?debug("gsms_router: interface ~p died, reason ~p\n",
+ [_If, _Reason]),
+ %% Restart done by gsms_if_sup
+ {noreply,State#state { ifs = Ifs }}
+ end;
+ {value,_S,Subs} ->
+ {noreply, State#state { subs = Subs} }
+ end;
+
+handle_info({input_from, BNumber, Pdu}, State) ->
+ lager:debug("input bnumber: ~p, pdu=~p\n", [BNumber,Pdu]),
+ lists:foreach(fun(S) -> match_filter(S, BNumber, Pdu) end,
+ State#state.subs),
+ {noreply, State};
+handle_info({timeout, Tmr, csq}, State) when State#state.csq_tmr =:= Tmr ->
+ Is =
+ lists:map(
+ fun(I) ->
+ R = gsms_0705:get_signal_strength(I#interface.pid),
+ lager:debug("csq result: ~p\n", [R]),
+ case R of
+ {ok,"+CSQ:"++Params} ->
+ case erl_scan:string(Params) of
+ {ok,[{integer,_,Rssi}|_],_} ->
+ Pdu = {rssi, Rssi},
+ BNumber = I#interface.bnumber,
+ if I#interface.rssi =/= Rssi ->
+ lists:foreach(
+ fun(S) ->
+ match_filter(S,BNumber,Pdu)
+ end, State#state.subs),
+ I#interface { rssi = Rssi };
+ true ->
+ I
+ end;
+ _Error ->
+ lager:error("unable to read rssi: ~p",
+ [_Error]),
+ I
+ end;
+ _Error ->
+ lager:error("unable to read rssi: ~p",[_Error]),
+ I
+ end
+ end, State#state.ifs),
+ Csq_ival = State#state.csq_ival,
+ Csq_tmr = if is_integer(Csq_ival), Csq_ival > 0 ->
+ erlang:start_timer(Csq_ival, self(), csq);
+ true -> undefined
+ end,
+ {noreply, State#state { ifs = Is, csq_tmr=Csq_tmr }};
+handle_info({'EXIT', Pid, Reason}, State) ->
+ case lists:keytake(Pid, #interface.pid, State#state.ifs) of
+ {value,_If,Ifs} ->
+ %% One of our interfaces died, log and ignore
+ ?debug("gsms_router: interface ~p died, reason ~p\n",
+ [_If, Reason]),
+ {noreply,State#state { ifs = Ifs }};
+ false ->
+ %% Someone else died, log and terminate
+ ?debug("gsms_router: linked process ~p died, reason ~p, terminating\n",
+ [Pid, Reason]),
+ {stop, Reason, State}
+ end;
+handle_info(_Info, State) ->
+ {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
+%%%===================================================================
+
+add_interface(Pid,BNumber,Attributes,State) ->
+ Mon = erlang:monitor(process, Pid),
+ I = #interface { pid=Pid, mon=Mon,
+ bnumber=BNumber, attributes=Attributes },
+ link(Pid),
+ State#state { ifs = [I | State#state.ifs ] }.
+
+match_filter(_S=#subscription {filter = Filter,
+ pid = Pid,
+ ref = Ref},
+ BNumber, Pdu) ->
+ lager:debug("match filter: ~p", [Filter]),
+ case match(Filter, BNumber, Pdu) of
+ true ->
+ lager:debug("filter match success.", []),
+ lager:debug("send to ~p", [Pid]),
+ Pid ! {gsms, Ref, Pdu};
+ false ->
+ lager:debug("filter match fail"),
+ ok
+ end.
+
+match(As, BNum, Sms) when is_list(As) ->
+ match_clause(As, BNum, Sms);
+match({'not',Filter}, BNum, Sms) ->
+ not match(Filter, BNum, Sms);
+match({'and',A,B}, BNum, Sms) ->
+ match(A,BNum,Sms) andalso match(B,BNum,Sms);
+match({'or',A,B}, BNum, Sms) ->
+ match(A,BNum,Sms) orelse match(B,BNum,Sms);
+match({bnumber,Addr}, BNum, _Sms) ->
+ %% receiving modem
+ match_addr(Addr, BNum);
+match(Match, _BNum, Sms) when is_record(Sms,gsms_deliver_pdu) ->
+ match_sms(Match, Sms);
+match({rssi,true}, _BNum, {rssi,_}) ->
+ true;
+match({creg,true}, _BNum, {creg,_}) ->
+ true;
+match(_, _BNum, _)->
+ false.
+
+match_clause([A|As], BNum, Sms) ->
+ case match(A, BNum, Sms) of
+ true -> match_clause(As, BNum, Sms);
+ false -> false
+ end;
+match_clause([], _BNum, _Sms) ->
+ true.
+
+match_sms({type,Type}, Sms) ->
+ case Sms#gsms_deliver_pdu.dcs of
+ [Type|_] -> true;
+ _ -> false
+ end;
+match_sms({alphabet,Alphabet}, Sms) ->
+ case Sms#gsms_deliver_pdu.dcs of
+ [_Type,_Compress,Alphabet|_] -> true;
+ _ -> false
+ end;
+match_sms({class,Class}, Sms) ->
+ case Sms#gsms_deliver_pdu.dcs of
+ [_Type,_Compress,_Alphabet,Class|_] -> true;
+ _ -> false
+ end;
+match_sms({pid,Pid}, Sms) ->
+ Sms#gsms_deliver_pdu.pid =:= Pid;
+match_sms({dst,Port}, Sms) ->
+ case lists:keyfind(1, port, Sms#gsms_deliver_pdu.udh) of
+ {port,Port,_} -> true;
+ _ -> false
+ end;
+match_sms({src,Port}, Sms) ->
+ case lists:keyfind(1, port, Sms#gsms_deliver_pdu.udh) of
+ {port,_,Port} -> true;
+ _ -> false
+ end;
+match_sms({anumber,Addr}, Sms) ->
+ match_addr(Addr, Sms#gsms_deliver_pdu.addr);
+match_sms({smsc,Addr}, Sms) ->
+ match_addr(Addr, Sms#gsms_deliver_pdu.smsc);
+match_sms({reg_exp, RegExp}, Sms) ->
+ match_body(RegExp, Sms#gsms_deliver_pdu.ud);
+match_sms({rssi,_}, _Sms) ->
+ false;
+match_sms({creg,_}, _Sms) ->
+ false.
+
+
+%% Add some more smart matching here to select international / national
+%% country suffix etc.
+match_addr(Addr, Addr) -> true;
+match_addr(Addr, #gsms_addr { addr = Addr }) when is_list(Addr) -> true;
+match_addr(#gsms_addr { type=unknown, addr=Addr},
+ #gsms_addr { addr=Addr}) -> true;
+match_addr(_, _) -> false.
+
+match_body(RegExp, UserData) ->
+ lager:debug("match regular expression: ~p", [RegExp]),
+ case re:run(UserData, RegExp, [{capture, none}]) of
+ match -> true;
+ nomatch -> false
+ end.
diff --git a/deps/gsms/src/gsms_sup.erl b/deps/gsms/src/gsms_sup.erl
new file mode 100644
index 0000000..7687476
--- /dev/null
+++ b/deps/gsms/src/gsms_sup.erl
@@ -0,0 +1,68 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2013, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @copyright (C) 2013, Tony Rogvall
+%%% @doc
+%%% Gsms supervisor
+%%%
+%%% Created: 28 Aug 2006 by Tony Rogvall
+%%% @end
+
+-module(gsms_sup).
+
+-behaviour(supervisor).
+
+-include("log.hrl").
+%% external exports
+-export([start_link/0,
+ start_link/1,
+ stop/0]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link(Args) ->
+ ?info("~p: start_link: args = ~p\n", [?MODULE, Args]),
+ case supervisor:start_link({local, ?MODULE}, ?MODULE, Args) of
+ {ok, Pid} ->
+ {ok, Pid, {normal, Args}};
+ Error ->
+ Error
+ end.
+
+start_link() ->
+ supervisor:start_link({local,?MODULE}, ?MODULE, []).
+
+stop() ->
+ exit(normal).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from supervisor
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%%----------------------------------------------------------------------
+init(Args) ->
+ ?info("~p: init: args = ~p,\n pid = ~p\n", [?MODULE, Args, self()]),
+ GsmsRouter = {gsms_router, {gsms_router, start_link, [Args]},
+ permanent, 5000, worker, [gsms_router]},
+ GsmsIfSup = {gsms_if_sup, {gsms_if_sup, start_link, []},
+ permanent, 5000, worker, [gsms_if_sup]},
+ {ok,{{one_for_all,3,5}, [GsmsRouter, GsmsIfSup]}}.
diff --git a/deps/gsms/src/gsms_uart.erl b/deps/gsms/src/gsms_uart.erl
new file mode 100644
index 0000000..4c5d0ee
--- /dev/null
+++ b/deps/gsms/src/gsms_uart.erl
@@ -0,0 +1,834 @@
+%%%---- BEGIN COPYRIGHT --------------------------------------------------------
+%%%
+%%% Copyright (C) 2007 - 2012, Rogvall Invest AB, <tony@rogvall.se>
+%%%
+%%% This software is licensed as described in the file COPYRIGHT, which
+%%% you should have received as part of this distribution. The terms
+%%% are also available at http://www.rogvall.se/docs/copyright.txt.
+%%%
+%%% You may opt to use, copy, modify, merge, publish, distribute and/or sell
+%%% copies of the Software, and permit persons to whom the Software is
+%%% furnished to do so, under the terms of the COPYRIGHT file.
+%%%
+%%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+%%% KIND, either express or implied.
+%%%
+%%%---- END COPYRIGHT ----------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @author Malotte Westman Lönne <malotte@malotte.net>
+%%% @copyright (C) 2012, Tony Rogvall
+%%% @doc
+%%% SMS command driver.
+%%%
+%%% Created : 1 Jul 2010 by Tony Rogvall
+%%% @end
+%%%-------------------------------------------------------------------
+-module(gsms_uart).
+
+-behaviour(gen_server).
+
+-include("log.hrl").
+
+%% API
+-export([start_link/1,
+ stop/1,
+ subscribe/1,
+ subscribe/2,
+ unsubscribe/2,
+ setopts/2,
+ at/2, atd/3, send/2]).
+
+%% Option processing
+-export([options/0,
+ split_opts/2,
+ normalise_opts/1,
+ validate_opts/1,
+ validate_opt/2]).
+
+-export([trimhd/1,trimtl/1,trim/1]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-define(SERVER, ?MODULE).
+-define(UART_DEFAULT_OPTS,
+ [{baud,115200},{mode,list},{active,true},
+ {packet,line},{csize,8},{parity,none},{stopb,1}]).
+-define(CTRL_Z, 16#1A).
+-define(ESC, 16#1B).
+
+%% For dialyzer
+-type gsms_uart_option() :: device | reopen_timeout | reply_timeout.
+
+-type gsms_uart_config() ::
+ {device, string()} |
+ {reopen_timeout, timeout()} |
+ {reply_timeout, timeout()} |
+ {smsc, string()}.
+
+-record(subscription,
+ {
+ pid,
+ mon,
+ pattern
+ }).
+
+-record(qent,
+ {
+ from, %% caller tag
+ mon, %% caller/pid monitor
+ command %% call or cast
+ }).
+
+-record(ctx,
+ {
+ uart, %% serial port descriptor
+ device, %% device string
+ caller, %% parent pid
+ uopts=[], %% uart options
+ opts=[], %% sms options
+ command="", %% last command
+ reply=[], %% list of reply line data
+ client, %% last client
+ client_data, %% ATD hex data if or undefined
+ queue = [], %% request queue [#qent{}]
+ reply_timer, %% timeout waiting for reply
+ reopen_timer, %% timer ref
+ subs = [] %% #subscription{}
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-spec options() -> [uart:uart_option()|gsms_uart_option()].
+
+options() ->
+ uart:options() ++
+ [
+ smsc,
+ reopen_timeout,
+ reply_timeout
+ ].
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server.
+%%
+%% Device contains the path to the Device. <br/>
+%% reopen_timeout =/= 0 means that if the driver fails to open the device it
+%% will try again in Timeout milliseconds.<br/>
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec start_link([gsms_uart_config()]) ->
+ {ok, Pid::pid()} |
+ ignore |
+ {error, Error::term()}.
+%%
+%% HUAWEI: uses device /dev/tty.HUAWEIMobile-Pcui
+%% for SMS services to be able to get notifications which are
+%% NOT available on /dev/tty.HUAWEIMobile-Modem
+%%
+start_link(Opts) ->
+ lager:info("~p: start_link: args = ~p\n", [?MODULE, Opts]),
+ gen_server:start_link(?MODULE, [self(),Opts], []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Stops the server.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec stop(Drv::pid()) -> ok | {error, Error::term()}.
+
+stop(Drv) ->
+ gen_server:call(Drv, stop).
+
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Subscribe to sms events.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec subscribe(Drv::pid()) -> {ok,reference()} | {error, Error::term()}.
+
+subscribe(Drv) ->
+ subscribe(Drv, []).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Subscribe to sms events.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec subscribe(Drv::pid(),Pattern::[{atom(),string()}]) ->
+ {ok,reference()} | {error, Error::term()}.
+subscribe(Drv,Pattern) ->
+ gen_server:call(Drv, {subscribe,self(),Pattern}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Unsubscribe from sms events.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec unsubscribe(Drv::pid(),Ref::reference()) -> ok | {error, Error::term()}.
+unsubscribe(Drv,Ref) ->
+ gen_server:call(Drv, {unsubscribe,Ref}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Set various options from uart options to smsc options
+%%
+%% @end
+%%--------------------------------------------------------------------
+
+setopts(Drv,Opts) when is_list(Opts) ->
+ gen_server:call(Drv, {setopts, Opts}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Run a raw command
+%%
+%% @end
+%%--------------------------------------------------------------------
+
+%% normal at command
+at(Drv,Command) ->
+ gen_server:call(Drv, {at,Command}).
+
+%% send command and data in data-enter state
+atd(Drv, Command, Hex) ->
+ gen_server:call(Drv, {atd,Command,Hex}, 20000).
+
+send(Drv, Data) ->
+ gen_server:call(Drv, {send,Data}).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%% @private
+%%--------------------------------------------------------------------
+%% @doc
+%% Initializes the server
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec init([Caller::pid()|gsms_uart_config()]) ->
+ {ok, Ctx::#ctx{}} |
+ {ok, Ctx::#ctx{}, Timeout::timeout()} |
+ ignore |
+ {stop, Reason::term()}.
+
+init([Caller,Opts]) ->
+ lager:info("~p: init: args = ~p,\n pid = ~p\n",
+ [?MODULE, Opts, self()]),
+ Opts1 = normalise_opts(?UART_DEFAULT_OPTS ++ Opts),
+ {Uopts0,Opts2} = split_opts(Opts1, uart:options()),
+ {Gopts0,Opts3} = split_opts(Opts2, options()),
+ case check_options(Uopts0,Gopts0,Opts3) of
+ ok ->
+ Uopts1 = proplists:delete(device, Uopts0),
+ Device = case proplists:get_value(device, Uopts0) of
+ undefined ->
+ case os:getenv("GSMS_DEVICE") of
+ false -> "";
+ Name -> Name
+ end;
+ Name -> Name
+ end,
+ S = #ctx { device = Device,
+ uopts = Uopts1,
+ caller = Caller,
+ opts = Gopts0,
+ queue = []
+ },
+ case open(S) of
+ {ok, S1} -> {ok, S1};
+ Error -> {stop, Error}
+ end;
+ Error ->
+ {stop, Error}
+ end.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+
+handle_call({setopts, Opts},_From, Ctx=#ctx { uart = U}) ->
+ lager:debug("setopts ~p", [Opts]),
+ Opts1 = normalise_opts(Opts),
+ {Uopts0,Opts2} = split_opts(Opts1, uart:options()),
+ {Gopts0,Opts3} = split_opts(Opts2, options()),
+ case check_options(Uopts0,Gopts0,Opts3) of
+ ok ->
+ Uopts1 = proplists:delete(device, Uopts0),
+ case proplists:get_value(device, Uopts0) of
+ Device when is_list(Device), Device =/= Ctx#ctx.device ->
+ Uopts2 = normalise_opts(Ctx#ctx.uopts ++ Uopts1),
+ Ctx1 = close(Ctx),
+ Ctx2 = Ctx1#ctx { uart=undefined,
+ reopen_timer = undefined,
+ device=Device,
+ uopts=Uopts2,
+ opts=Gopts0 },
+ case open(Ctx2) of
+ {ok, Ctx3} -> {reply,ok,Ctx3};
+ Error -> {stop, Error, Ctx2}
+ end;
+ _ ->
+ case U of
+ undefined ->
+ {reply, {error,no_port}, Ctx};
+ simulated ->
+ {reply, ok, Ctx#ctx { uopts=Uopts1, opts=Gopts0} };
+ _ ->
+ lager:debug("uart:setopts ~p", [Uopts1]),
+ case uart:setopts(U, Uopts1) of
+ ok ->
+ {reply, ok, Ctx#ctx { uopts=Uopts1,
+ opts=Gopts0} };
+ Error ->
+ {reply, Error, Ctx}
+ end
+ end
+ end;
+ Error ->
+ {reply, Error, Ctx}
+ end;
+
+handle_call({subscribe,Pid,Pattern},_From,Ctx=#ctx { subs=Subs}) ->
+ Mon = erlang:monitor(process, Pid),
+ Subs1 = [#subscription { pid = Pid, mon = Mon, pattern = Pattern}|Subs],
+ {reply, {ok,Mon}, Ctx#ctx { subs = Subs1}};
+
+handle_call({unsubscribe,Ref},_From,Ctx) ->
+ erlang:demonitor(Ref),
+ Ctx1 = remove_subscription(Ref,Ctx),
+ {reply, ok, Ctx1};
+handle_call(stop, _From, Ctx) ->
+ {stop, normal, ok, Ctx};
+
+
+%% other commands we queue if gsms_uart is busy processing command
+handle_call(Call,From,Ctx=#ctx {client = Client})
+ when Client =/= undefined andalso Call =/= stop ->
+ %% Driver is busy ..
+ lager:debug("handle_call: Driver busy, store call ~p", [Call]),
+ %% set timer already here? probably!
+ {Pid,_Tag} = From,
+ Mon = erlang:monitor(process, Pid),
+ Timeout=proplists:get_value(reply_timeout,Ctx#ctx.opts,5000),
+ QE = #qent { from=From, mon=Mon, command={call,Call}},
+ Q = Ctx#ctx.queue ++ [QE],
+ {noreply, Ctx#ctx { queue = Q }, Timeout};
+
+handle_call({at,Command},From,Ctx) ->
+ lager:debug("handle_call: command ~p", [Command]),
+ case Ctx#ctx.uart of
+ simulated ->
+ lager:info("simulated output ~p\n", [Command]),
+ {reply, ok, Ctx};
+ undefined ->
+ lager:info("~p: No port defined yet.\n", [?MODULE]),
+ {reply, {error,no_port}, Ctx};
+ U ->
+ case uart:send(U, ["AT",Command,"\r\n"]) of
+ ok ->
+ lager:debug("command: sent"),
+ %% Wait for confirmation
+ Tm=proplists:get_value(reply_timeout,Ctx#ctx.opts,5000),
+ TRef = erlang:start_timer(Tm, self(), reply),
+ {noreply,Ctx#ctx {command = Command,
+ client=From,
+ client_data = undefined,
+ reply = [],
+ reply_timer = TRef}};
+ Other ->
+ lager:debug("command: send failed, reason ~p", [Other]),
+ {reply, Other, Ctx}
+ end
+ end;
+
+handle_call({atd,Command,Hex},From,Ctx) ->
+ lager:debug("handle_call: command ~p", [Command]),
+ case Ctx#ctx.uart of
+ simulated ->
+ lager:info("simulated output ~p\n", [Command]),
+ {reply, ok, Ctx};
+ undefined ->
+ lager:info("~p: No port defined yet.\n", [?MODULE]),
+ {reply, {error,no_port}, Ctx};
+ U ->
+ uart:setopts(U, [{active,false}]),
+ case uart:send(U, ["AT",Command,"\r\n"]) of
+ ok ->
+ lager:debug("command: sent\n", []),
+ %% wait for exacly ">\r\n"
+ uart:setopts(U, [{active,once},{packet,{size,3}}]),
+ %% Wait for confirmation
+ Tm=proplists:get_value(reply_timeout,Ctx#ctx.opts,5000),
+ TRef = erlang:start_timer(Tm, self(), reply),
+ {noreply,Ctx#ctx {command = Command,
+ client=From,
+ client_data=Hex,
+ reply = [],
+ reply_timer = TRef}};
+ Other ->
+ uart:setopts(U, [{active,true}]),
+ lager:debug("command: send failed, reason ~p", [Other]),
+ {reply, Other, Ctx}
+ end
+ end;
+
+handle_call({send,Data},_From,Ctx) ->
+ lager:debug("handle_call: send ~p", [Data]),
+ case Ctx#ctx.uart of
+ simulated ->
+ lager:info("simulated output ~p\n", [Data]),
+ {reply, ok, Ctx};
+ undefined ->
+ lager:info("~p: No port defined yet.\n", [?MODULE]),
+ {reply, {error,no_port}, Ctx};
+ U ->
+ Reply = uart:send(U, Data),
+ {reply, Reply, Ctx}
+ end;
+handle_call(_Request, _From, Ctx) ->
+ {reply, {error,bad_call}, Ctx}.
+
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec handle_cast(Msg::term(), Ctx::#ctx{}) ->
+ {noreply, Ctx::#ctx{}} |
+ {stop, Reason::term(), Ctx::#ctx{}}.
+
+
+handle_cast(Cast, Ctx=#ctx {uart = U, client=Client})
+ when U =/= undefined, Client =/= undefined ->
+ lager:debug("handle_cast: Driver busy, store cast ~p", [Cast]),
+ %% FIXME: add timer when/if we start using this
+ QE = #qent { from=undefined, mon=undefined, command = {cast,Cast}},
+ Q = Ctx#ctx.queue ++ [QE],
+ {noreply, Ctx#ctx { queue = Q }};
+
+handle_cast(_Msg, Ctx) ->
+ lager:debug("handle_cast: Unknown message ~p", [_Msg]),
+ {noreply, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @end
+%%--------------------------------------------------------------------
+-type info()::
+ {uart, U::port(), Data::binary()} |
+ {uart_error, U::port(), Reason::term()} |
+ {uart_closed, U::port()} |
+ {timeout, reference(), reply} |
+ {timeout, reference(), reopen} |
+ {'DOWN',Ref::reference(),process,pid(),Reason::term()}.
+
+-spec handle_info(Info::info(), Ctx::#ctx{}) ->
+ {noreply, Ctx::#ctx{}} |
+ {stop, Reason::term(), Ctx::#ctx{}}.
+
+handle_info({timeout,TRef,reply},
+ Ctx=#ctx {client=Client, reply_timer=TRef}) ->
+ lager:debug("handle_info: timeout waiting for port", []),
+ gen_server:reply(Client, {error, port_timeout}),
+ Ctx1 = Ctx#ctx { reply_timer=undefined, reply=[], client = undefined},
+ next_command(Ctx1);
+
+handle_info({uart,U,Data}, Ctx) when U =:= Ctx#ctx.uart, is_binary(Data) ->
+ lager:debug("handle_info: noreply ~p", [Data]),
+ send_event(Ctx#ctx.subs, {data,Data}),
+ {noreply,Ctx};
+handle_info({uart,U,Data}, Ctx) when U =:= Ctx#ctx.uart ->
+ lager:debug("got uart data: ~p\n", [Data]),
+ case trim(Data) of
+ "" -> %% empty line (may add this later?)
+ {noreply, Ctx};
+ ">" when Ctx#ctx.client_data =/= undefined ->
+ uart:setopts(U, [{active,true},{packet,line}]),
+ case uart:send(U, [Ctx#ctx.client_data,?CTRL_Z]) of
+ ok ->
+ lager:debug("data sent ~p\n", [Ctx#ctx.client_data]),
+ stop_timer(Ctx#ctx.reply_timer),
+ Tm=proplists:get_value(reply_timeout,Ctx#ctx.opts,10000),
+ TRef = start_timer(Tm, reply),
+ {noreply,Ctx#ctx { reply_timer=TRef }};
+ Error ->
+ lager:debug("command: send failed, reason ~p", [Error]),
+ reply(Error, Ctx)
+ end;
+ "OK" ->
+ reply(ok, Ctx);
+ "ERROR" ->
+ reply(error, Ctx);
+ "+CMS ERROR:"++Code ->
+ reply(error, Ctx#ctx { reply=[trimhd(Code)|Ctx#ctx.reply]});
+ "+CMTI:"++EventData -> %% new SMS message (stored) arrived
+ Ctx1 = event_notify(cmti,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CMT:"++EventData -> %% new SMS message arrived
+ Ctx1 = event_notify(cmt,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CMGS:"++EventData -> %% Send SMS response code
+ Ctx1 = event_notify(cmgs,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CDSI:"++EventData -> %% SMS status report (stored) arrived
+ Ctx1 = event_notify(cdsi,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "+CDS:"++EventData -> %% SMS status report
+ Ctx1 = event_notify(cds,trimhd(EventData), Ctx),
+ {noreply, Ctx1};
+ "^"++EventData -> %% Periodic information
+ Ctx1 = event_notify(periodic,trimhd(EventData), Ctx),
+ {noreply,Ctx1};
+ Reply ->
+ if Ctx#ctx.client =/= undefined ->
+ lager:debug("handle_info: data ~p", [Reply]),
+ {noreply,Ctx#ctx { reply=[Reply|Ctx#ctx.reply]}};
+ true ->
+ lager:debug("handle_info: noreply ~p", [Reply]),
+ send_event(Ctx#ctx.subs, {data,Data}),
+ {noreply,Ctx}
+ end
+ end;
+
+handle_info({uart_error,U,Reason}, Ctx) when U =:= Ctx#ctx.uart ->
+ if Reason =:= enxio ->
+ lager:error("uart error ~p device ~s unplugged?",
+ [Reason,Ctx#ctx.device]);
+ true ->
+ lager:error("uart error ~p for device ~s",
+ [Reason,Ctx#ctx.device])
+ end,
+ {noreply, Ctx};
+
+handle_info({uart_closed,U}, Ctx) when U =:= Ctx#ctx.uart ->
+ uart:close(U),
+ lager:error("uart close device ~s will retry", [Ctx#ctx.device]),
+ case open(Ctx#ctx { uart=undefined}) of
+ {ok, Ctx1} -> {noreply, Ctx1};
+ Error -> {stop, Error, Ctx}
+ end;
+
+handle_info({timeout,Ref,reopen}, Ctx) when Ctx#ctx.reopen_timer =:= Ref ->
+ case open(Ctx#ctx { uart=undefined, reopen_timer=undefined}) of
+ {ok, Ctx1} -> {noreply, Ctx1};
+ Error -> {stop, Error, Ctx}
+ end;
+
+handle_info({'DOWN',Ref,process,_Pid,_Reason},Ctx) ->
+ lager:debug("handle_info: subscriber ~p terminated: ~p",
+ [_Pid, _Reason]),
+ case lists:keytake(Ref,#qent.mon, Ctx#ctx.queue) of
+ false -> remove_subscription(Ref,Ctx);
+ {value,_QE,Q} ->
+ {noreply,Ctx#ctx { queue=Q}}
+ end;
+handle_info(_Info, Ctx) ->
+ lager:debug("handle_info: Unknown info ~p", [_Info]),
+ {noreply, Ctx}.
+
+%%--------------------------------------------------------------------
+%% @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.
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec terminate(Reason::term(), Ctx::#ctx{}) ->
+ ok.
+
+terminate(_Reason, Ctx) ->
+ close(Ctx),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process ctx when code is changed
+%%
+%% @end
+%%--------------------------------------------------------------------
+-spec code_change(OldVsn::term(), Ctx::#ctx{}, Extra::term()) ->
+ {ok, NewCtx::#ctx{}}.
+
+code_change(_OldVsn, Ctx, _Extra) ->
+ {ok, Ctx}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+check_options(Uopts,Gopts,[]) ->
+ case uart:validate_opts(Uopts) of
+ ok -> validate_opts(Gopts);
+ Error -> Error
+ end;
+check_options(_Uopt,_Gopts,Opts) ->
+ {error, {unknown_opts,Opts}}.
+
+
+open(Ctx=#ctx {device = ""}) ->
+ lager:debug("open: simulated\n", []),
+ {ok, Ctx#ctx { uart=simulated }};
+
+open(Ctx=#ctx {device = Name, uopts=UOpts }) ->
+ case uart:open(Name,UOpts) of
+ {ok,U} ->
+ lager:debug("open: ~s [~w] [~w]: ~p",
+ [Name,UOpts,uart:getopts(U,uart:options()),U]),
+ flush_uart(U),
+ lager:debug("sync start"),
+ %% waky, waky ? do not echo
+ uart:send(U, [?ESC|"AT\r\n"]), %% escape if stuck in message send
+ flush_uart(U, 1000, 0),
+ uart:send(U, "ATZ\r\n"), %% reset
+ flush_uart(U, 2000, 0),
+ uart:send(U, "ATE0\r\n"), %% echo off
+ flush_uart(U, 1000, 0),
+ uart:send(U, "AT\r\n"), %% empty
+ flush_uart(U, 100, 0),
+ uart:send(U, "AT\r\n"), %% empty
+ flush_uart(U, 100, 0),
+ lager:debug("sync stop"),
+ %% signal that the the uart device is up running
+ Ctx#ctx.caller ! {gsms_uart, self(), up},
+ {ok, Ctx#ctx { uart=U }};
+ {error, E} when E == eaccess;
+ E == enoent ->
+ case proplists:get_value(reopen_timeout,Ctx#ctx.opts,infinity) of
+ infinity ->
+ lager:debug("open: Driver not started, reason = ~p.\n",[E]),
+ {error, E};
+ Ival ->
+ lager:debug("open: uart could not be opened, will try again"
+ " in ~p millisecs.\n", [Ival]),
+ Reopen_timer = start_timer(Ival, reopen),
+ {ok, Ctx#ctx { reopen_timer = Reopen_timer }}
+ end;
+
+ Error ->
+ lager:debug("open: Driver not started, reason = ~p.\n",
+ [Error]),
+ Error
+ end.
+
+close(Ctx=#ctx {uart = U}) when is_port(U) ->
+ lager:debug("close: ~p", [U]),
+ uart:close(U),
+ {ok, Ctx#ctx { uart=undefined }};
+close(Ctx) ->
+ {ok, Ctx}.
+
+
+%% flush uart data messages
+flush_uart(U) ->
+ flush_uart(U, 1000, 250).
+
+flush_uart(U,T0,T1) ->
+ receive
+ {uart,U,_Data} ->
+ lager:debug("flush: uart ~p\n", [_Data]),
+ flush_uart(U,T1)
+ after T0 ->
+ ok
+ end.
+
+flush_uart(U,T) ->
+ receive
+ {uart,U,_Data} ->
+ lager:debug("flush: uart ~p\n", [_Data]),
+ flush_uart(U,T)
+ after T ->
+ ok
+ end.
+
+start_timer(0, _Message) -> undefined;
+start_timer(infinity, _Message) -> undefined;
+start_timer(Time, Message) -> erlang:start_timer(Time, self(), Message).
+
+stop_timer(undefined) -> ok;
+stop_timer(Ref) when is_reference(Ref) ->
+ erlang:cancel_timer(Ref),
+ receive
+ {timeout,Ref,_} -> ok
+ after 0 ->
+ ok
+ end.
+
+
+reply(Tag, Ctx) ->
+ if Ctx#ctx.client =/= undefined ->
+ stop_timer(Ctx#ctx.reply_timer),
+ case lists:reverse(Ctx#ctx.reply) of
+ [] ->
+ gen_server:reply(Ctx#ctx.client, Tag);
+ [Response] ->
+ gen_server:reply(Ctx#ctx.client, {Tag,Response});
+ MultiResponse ->
+ gen_server:reply(Ctx#ctx.client, {Tag,MultiResponse})
+ end,
+ Ctx1 = Ctx#ctx { client=undefined,
+ client_data = undefined,
+ reply_timer=undefined,
+ command = "", reply=[] },
+ next_command(Ctx1);
+ true ->
+ {noreply, Ctx}
+ end.
+
+next_command(Ctx) ->
+ case Ctx#ctx.queue of
+ [#qent{from=From,mon=Mon,command={call,Call}} | Q1] ->
+ case handle_call(Call, From, Ctx#ctx { queue=Q1}) of
+ {reply,Reply,Ctx1} ->
+ gen_server:reply(From,Reply),
+ erlang:demonitor(Mon,[flush]),
+ next_command(Ctx1);
+ CallResult ->
+ CallResult
+ end;
+ [#qent {command={cast,Cast}} | Q1] ->
+ handle_cast(Cast, Ctx#ctx { queue=Q1});
+ [] ->
+ uart:setopts(Ctx#ctx.uart, [{active,true}]),
+ {noreply, Ctx#ctx { queue=[]}}
+ end.
+
+trimhd([$\s|Cs]) -> trimhd(Cs);
+trimhd([$\t|Cs]) -> trimhd(Cs);
+trimhd([$\r|Cs]) -> trimhd(Cs);
+trimhd([$\n|Cs]) -> trimhd(Cs);
+trimhd([0|Cs]) -> trimhd(Cs);
+trimhd(Cs) -> Cs.
+
+trimtl(Cs) -> lists:reverse(trimhd(lists:reverse(Cs))).
+
+trim(Cs) -> trimtl(trimhd(Cs)).
+
+unquote([$"|Cs]) ->
+ case lists:reverse(Cs) of
+ [$"|Cs1] -> lists:reverse(Cs1);
+ Cs1 -> Cs1
+ end;
+unquote(Cs1) -> Cs1.
+
+to_integer(Cs) ->
+ try erlang:list_to_integer(Cs, 10) of
+ Value -> Value
+ catch
+ error:_ -> Cs
+ end.
+
+remove_subscription(Ref, Ctx=#ctx { subs=Subs}) ->
+ Subs1 = lists:keydelete(Ref, #subscription.mon, Subs),
+ Ctx#ctx { subs = Subs1 }.
+
+
+event_notify(Name,String, Ctx) ->
+ Args =
+ case string:tokens(String, ",") of
+ [Store,Index] ->
+ [{"store",unquote(Store)},{"index",to_integer(Index)}];
+ Items ->
+ [{"items", Items}]
+ end,
+ Event = {Name,Args},
+ lager:debug("Event: ~p", [Event]),
+ send_event(Ctx#ctx.subs, Event),
+ Ctx.
+
+send_event([#subscription{pid=Pid,mon=Ref,pattern=Pattern}|Tail], Event) ->
+ case match_event(Pattern, Event) of
+ true -> Pid ! {gsms_event,Ref,Event};
+ false -> false
+ end,
+ send_event(Tail,Event);
+send_event([],_Event) ->
+ ok.
+
+match_event([], _) -> true;
+match_event([{Key,ValuePat}|Kvs],Event) ->
+ case lists:keyfind(Key, 1, Event) of
+ {Key,ValuePat} -> match_event(Kvs, Event);
+ _ -> false
+ end.
+
+%% split options in two groups {A,B}
+%% where A is the group with all keys in Keys B are the rest of the options
+split_opts(List, Keys) ->
+ {Lists,Rest} = proplists:split(List, Keys),
+ List1 = %% get last element from each list, simulate seq setting
+ lists:foldr(fun([],Acc) -> Acc;
+ (L,Acc) -> [lists:last(L)|Acc]
+ end, [], Lists),
+ {List1,Rest}.
+
+%% remove duplicate options keep later than earlier options
+%% normalise boolean options
+normalise_opts([Opt|Opts]) ->
+ case Opt of
+ Kv={Key,_} ->
+ case proplists:is_defined(Key, Opts) of
+ true -> normalise_opts(Opts);
+ false -> [Kv|normalise_opts(Opts)]
+ end;
+ Key ->
+ case proplists:is_defined(Key, Opts) of
+ true -> normalise_opts(Opts);
+ false -> [{Key,true}|normalise_opts(Opts)]
+ end
+ end;
+normalise_opts([]) ->
+ [].
+
+validate_opts([{K,V}|Kvs]) ->
+ case validate_opt(K,V) of
+ true -> validate_opts(Kvs);
+ false -> {error,{type_error,K,V}};
+ undefined -> {error,{unknown_opt,K}};
+ Error -> Error
+ end;
+validate_opts([]) ->
+ ok.
+
+validate_opt(device, Arg) -> is_list(Arg);
+validate_opt(reopen_timeout, Arg) -> is_timeout(Arg);
+validate_opt(reply_timeout, Arg) -> is_timeout(Arg);
+validate_opt(smsc, Arg) -> is_list(Arg);
+validate_opt(_,_Arg) -> undefined.
+
+is_timeout(T) ->
+ (T =:= infinity) orelse
+ (is_integer(T) andalso (T>=0)).
+
diff --git a/deps/gsms/src/log.hrl b/deps/gsms/src/log.hrl
new file mode 100644
index 0000000..270f06c
--- /dev/null
+++ b/deps/gsms/src/log.hrl
@@ -0,0 +1,44 @@
+%%
+%% Log macros
+%%
+-ifndef(__LOG_HRL__).
+-define(__LOG_HRL__, true).
+
+-compile({parse_transform, lager_transform}).
+
+%% Lager logging levels
+%% debug, info, notice, warning, error, critical, alert, emergency, none.
+
+-define(debug(Fmt), lager:debug(Fmt)).
+-define(debug(Fmt, Args), lager:debug(Fmt, Args)).
+-define(debug(Attrs, Fmt, Args), lager:debug(Attrs, Fmt, Args)).
+
+-define(info(Fmt), lager:info(Fmt)).
+-define(info(Fmt, Args), lager:info(Fmt, Args)).
+-define(info(Attrs, Fmt, Args), lager:info(Attrs, Fmt, Args)).
+
+-define(notice(Fmt), lager:notice(Fmt)).
+-define(notice(Fmt, Args), lager:notice(Fmt, Args)).
+-define(notice(Attrs, Fmt, Args), lager:notice(Attrs, Fmt, Args)).
+
+-define(warning(Fmt), lager:warning(Fmt)).
+-define(warning(Fmt, Args), lager:warning(Fmt, Args)).
+-define(warning(Attrs, Fmt, Args), lager:warning(Attrs, Fmt, Args)).
+
+-define(error(Fmt), lager:error(Fmt)).
+-define(error(Fmt, Args), lager:error(Fmt, Args)).
+-define(error(Attrs, Fmt, Args), lager:error(Attrs, Fmt, Args)).
+
+-define(critical(Fmt), lager:critical(Fmt)).
+-define(critical(Fmt, Args), lager:critical(Fmt, Args)).
+-define(critical(Attrs, Fmt, Args), lager:critical(Attrs, Fmt, Args)).
+
+-define(alert(Fmt), lager:alert(Fmt)).
+-define(alert(Fmt, Args), lager:alert(Fmt, Args)).
+-define(alert(Attrs, Fmt, Args), lager:alert(Attrs, Fmt, Args)).
+
+-define(emergency(Fmt), lager:emergency(Fmt)).
+-define(emergency(Fmt, Args), lager:emergency(Fmt, Args)).
+-define(emergency(Attrs, Fmt, Args), lager:emergency(Attrs, Fmt, Args)).
+
+-endif.
diff --git a/deps/gsms/src/sim900 b/deps/gsms/src/sim900
new file mode 100755
index 0000000..40f646f
--- /dev/null
+++ b/deps/gsms/src/sim900
@@ -0,0 +1,80 @@
+#!/usr/bin/env escript
+
+%% GPIO map
+-define(NRST, 23). %% output pin
+-define(PWRKEY, 24). %% output pin
+-define(RI, 17). %% input
+-define(DTR, 22). %% output pin
+
+main([]) ->
+ usage(0);
+main(["help"|_Options]) ->
+ usage(0);
+main(["-h"|_Options]) ->
+ usage(0);
+main(Options) ->
+ case string:to_integer(os:cmd("id -u")) of
+ {0, _} ->
+ %% require root access
+ application:start(gpio),
+ command(Options),
+ halt(0);
+ {_,_} ->
+ io:fwrite(escript:script_name() ++ " need root privileges\n"),
+ halt(1)
+ end.
+
+command(["init"|Cmds]) -> %% init only
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,low),
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST,low),
+ gpio:init(?RI),
+ gpio:init(?DTR),
+ gpio:set_direction(?DTR,low),
+ command(Cmds);
+command(["pwron"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(2200+10),
+ command(Cmds);
+command(["pwroff"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["pwrrst"|Cmds]) -> %% combined pwr
+ command(["pwroff"]),
+ timer:sleep(500+10), %% >500ms
+ command(["pwron"]),
+ command(Cmds);
+command(["rst"|Cmds]) ->
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST, out),
+ gpio:set(?NRST),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?NRST),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["help"|_Cmds]) ->
+ usage(0);
+command(["-h"|_Cmds]) ->
+ usage(0);
+command([_|_Cmds]) ->
+ usage(1);
+command([]) ->
+ ok.
+
+help() ->
+ io:fwrite("usage: "++ escript:script_name() ++
+ " [-h] help|init|pwron|pwroff|pwrrst|rst\n").
+
+usage(N) ->
+ help(),
+ halt(N).
diff --git a/deps/gsms/src/sim900_ft1075k b/deps/gsms/src/sim900_ft1075k
new file mode 100755
index 0000000..98a7c51
--- /dev/null
+++ b/deps/gsms/src/sim900_ft1075k
@@ -0,0 +1,75 @@
+#!/usr/bin/env escript
+
+%% GPIO map
+-define(NRST, 22). %% output pin 15
+-define(PWRKEY, 27). %% output pin 13
+
+main([]) ->
+ usage(0);
+main(["help"|_Options]) ->
+ usage(0);
+main(["-h"|_Options]) ->
+ usage(0);
+main(Options) ->
+ case string:to_integer(os:cmd("id -u")) of
+ {0, _} ->
+ %% require root access
+ application:start(gpio),
+ command(Options),
+ halt(0);
+ {_,_} ->
+ io:fwrite(escript:script_name() ++ " need root privileges\n"),
+ halt(1)
+ end.
+
+command(["init"|Cmds]) -> %% init only
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,low),
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST,low),
+ command(Cmds);
+command(["pwron"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(2200+10),
+ command(Cmds);
+command(["pwroff"|Cmds]) ->
+ gpio:init(?PWRKEY),
+ gpio:set_direction(?PWRKEY,out),
+ gpio:set(?PWRKEY),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?PWRKEY),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["pwrrst"|Cmds]) -> %% combined pwr
+ command(["pwroff"]),
+ timer:sleep(500+10), %% >500ms
+ command(["pwron"]),
+ command(Cmds);
+command(["rst"|Cmds]) ->
+ gpio:init(?NRST),
+ gpio:set_direction(?NRST, out),
+ gpio:set(?NRST),
+ timer:sleep(1000+10), %% >1s
+ gpio:clr(?NRST),
+ timer:sleep(1700+10),
+ command(Cmds);
+command(["help"|_Cmds]) ->
+ usage(0);
+command(["-h"|_Cmds]) ->
+ usage(0);
+command([_|_Cmds]) ->
+ usage(1);
+command([]) ->
+ ok.
+
+help() ->
+ io:fwrite("usage: "++ escript:script_name() ++
+ " [-h] help|init|pwron|pwroff|pwrrst|rst\n").
+
+usage(N) ->
+ help(),
+ halt(N).
diff --git a/deps/gsms/sys.config b/deps/gsms/sys.config
new file mode 100644
index 0000000..3db5e31
--- /dev/null
+++ b/deps/gsms/sys.config
@@ -0,0 +1,47 @@
+%% -*- erlang -*-
+%%
+%% Configuration file for included erlang applications.
+%%
+[
+ %% SASL config
+ {sasl, [
+ {sasl_error_logger, {file, "log/sasl-error.log"}},
+ {errlog_type, error},
+ {error_logger_mf_dir, "log/sasl"}, % Log directory
+ {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size
+ {error_logger_mf_maxfiles, 5} % 5 files max
+ ]},
+ {lager, [
+ {handlers, [
+ {lager_console_backend, info},
+ {lager_file_backend,
+ [
+ {"log/lager/error.log", error, 10485760, "$D0", 5},
+ {"log/lager/console.log", info, 10485760, "$D0", 5}
+ ]}
+ ]}
+ ]},
+{ale,
+ [{init_traces, [
+%% {[{module, gsms_uart}], debug},
+%% {[{module, gsms_0705}], debug},
+%% {[{module, gsms_router}], debug}
+ ]}]},
+ %%
+ %% GSMS interfaces (example!)
+ %%
+ {gsms, [{interfaces,
+ [
+ {gsms_0705, 1, [{device,"/dev/tty.usbserial-FTF5DP2J"},
+ {bnumber, "<phone-number>"},{baud,19200},
+ {reopen_timeout, 5000}]}
+ {gsms_0705, 2, [{device,"/dev/tty.HUAWEIMobile-Pcui"},
+ {bnumber, "<phone-number>"},
+ {reopen_timeout, 5000}]}
+ {gsms_0705, 3, [{device, "/dev/tty.usbserial"},
+ {bnumber, "<phone-number>"},
+ {baud, 9600}]}
+ ]}
+ ]}
+
+].
diff --git a/deps/gsms/tetrapak/config.ini b/deps/gsms/tetrapak/config.ini
new file mode 100644
index 0000000..1574279
--- /dev/null
+++ b/deps/gsms/tetrapak/config.ini
@@ -0,0 +1,13 @@
+[build]
+version = "~t.~o~~~c"
+
+[package]
+maintainer = "Magnus Feuer <magnus@feuerlabs.com>"
+exclude = "\\.gitignore|README.md"
+architecture = host
+
+[xref]
+ignore_undef = [
+ { gsms_uart,eat, 2 },
+ { random, unifrom, 1 }
+ ]