Skip to content

Commit 7aeee33

Browse files
committed
Send RF quality data to applications
1 parent d21f7e1 commit 7aeee33

13 files changed

+77
-53
lines changed

doc/Handlers.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ release defined in the `rebar.config`:
4444

4545
## lorawan_application Behaviour
4646

47-
Your module needs to export `init/1`, `handle_join/3` and `handle_rx/4` functions.
47+
Your module needs to export `init/1`, `handle_join/3` and `handle_rx/5` functions.
4848

4949
### init(App)
5050

@@ -58,17 +58,20 @@ REST API or WebSockets). For more details see
5858
The `handle_join/3` will be called when a new node joins the network. The function
5959
shall return either `ok` or `{error, error_description}`.
6060

61-
### handle_rx(DevAddr, App, AppArgs, RxData)
61+
### handle_rx(DevAddr, App, AppArgs, RxData, RxQ)
62+
6263
The `handle_rx/4` will be called upon reception of a LoRaWAN frame:
6364
* *DevAddr* is the 4-byte device address
6465
* *App* is the application name defined in the `sys.config`
6566
* *AppArgs* is an opaque value assigned to the device
6667
* *RxData* is the #rxdata{} record with:
68+
* *fcnt*
6769
* *port* number
6870
* *data* binary
6971
* *last_lost* flag indicating that the last confirmed response got lost
7072
* *shall_reply* flag indicating the MAC has to reply to the device even if
7173
the application sends no data
74+
* *RxQ* contains the #rxq{} record with frame reception details
7275

7376
The *last_lost* flag allows the application to decide to send new data instead of
7477
retransmitting the old data.

include/lorawan.hrl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
% Distributed under the terms of the MIT License. See the LICENSE file.
55
%
66

7-
-record(rxq, {freq, datr, codr, time, tmst, erlst, chan, rfch, stat, rssi, lsnr}).
8-
-record(txq, {freq, datr, codr, tmst, rfch, powe}).
97
-record(stat, {time, lati, long, alti, rxnb, rxok, rxfw, ackr, dwnb, txnb}).
108

119
-record(ignored_link, {devaddr, mask}).

src/lorawan_admin.erl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ parse_admin(List) ->
7171
Key == devaddr; Key == nwkskey; Key == appskey -> {Key, lorawan_mac:hex_to_binary(Value)};
7272
({Key, Value}) when Key == gpspos -> {Key, parse_latlon(Value)};
7373
({Key, Value}) when Key == adr_use; Key == adr_set -> {Key, parse_adr(Value)};
74+
({Key, Value}) when Key == rxq -> {Key, ?to_record(rxq, parse_admin(Value))};
7475
({Key, Value}) when Key == txdata -> {Key, ?to_record(txdata, parse_admin(Value))};
7576
({Key, Value}) when Key == last_join; Key == last_rx; Key == devstat_time;
76-
Key == datetime -> {Key, iso8601:parse(Value)};
77+
Key == time; Key == datetime -> {Key, iso8601:parse_exact(Value)};
7778
({Key, Value}) when Key == devstat -> {Key, parse_devstat(Value)};
7879
(Else) -> Else
7980
end,
@@ -90,10 +91,13 @@ build_admin(List) ->
9091
Key == frid -> [{Key, lorawan_mac:binary_to_hex(Value)} | A];
9192
({Key, Value}, A) when Key == gpspos -> [{Key, build_latlon(Value)} | A];
9293
({Key, Value}, A) when Key == adr_use; Key == adr_set -> [{Key, build_adr(Value)} | A];
94+
({Key, Value}, A) when Key == rxq -> [{Key, build_admin(?to_proplist(rxq, Value))} | A];
9395
({Key, Value}, A) when Key == txdata -> [{Key, build_admin(?to_proplist(txdata, Value))} | A];
9496
({Key, Value}, A) when Key == last_join; Key == last_rx; Key == devstat_time;
95-
Key == datetime -> [{Key, iso8601:format(Value)} | A];
97+
Key == time; Key == datetime -> [{Key, iso8601:format(Value)} | A];
9698
({Key, Value}, A) when Key == devstat -> [{Key, build_devstat(Value)} | A];
99+
% hide very internal fields
100+
({Key, _Value}, A) when Key == srvtmst -> A;
97101
(Else, A) -> [Else | A]
98102
end,
99103
[], List).

src/lorawan_admin_rxgraph.erl

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,16 @@ get_rxframe(Req, #state{format=rgraph}=State) ->
5151
[{id, <<"datr">>}, {label, <<"Data Rate">>}, {type, <<"number">>}],
5252
[{id, <<"freq">>}, {label, <<"Frequency (MHz)">>}, {type, <<"number">>}]
5353
]},
54-
{rows, lists:map(fun(#rxframe{fcnt=FCnt, region=Region, datr=DatR, freq=Freq}) ->
54+
{rows, lists:map(
55+
fun(#rxframe{fcnt=FCnt, region=Region, rxq=#rxq{datr=DatR, freq=Freq}}) ->
5556
[{c, [
5657
[{v, FCnt}],
5758
[{v, lorawan_mac_region:datar_to_dr(Region, DatR)}, {f, DatR}],
5859
[{v, Freq}]
59-
]}]
60+
]}];
61+
% if there are some unexpected data, show nothing
62+
(_Else) ->
63+
[{c, []}]
6064
end, ActRec)
6165
}
6266
],
@@ -72,12 +76,15 @@ get_rxframe(Req, #state{format=qgraph}=State) ->
7276
[{id, <<"rssi">>}, {label, <<"RSSI (dBm)">>}, {type, <<"number">>}],
7377
[{id, <<"snr">>}, {label, <<"SNR (dB)">>}, {type, <<"number">>}]
7478
]},
75-
{rows, lists:map(fun(#rxframe{fcnt=FCnt, rssi=RSSI, lsnr=SNR}) ->
79+
{rows, lists:map(
80+
fun(#rxframe{fcnt=FCnt, rxq=#rxq{rssi=RSSI, lsnr=SNR}}) ->
7681
[{c, [
7782
[{v, FCnt}],
7883
[{v, RSSI}],
7984
[{v, SNR}]
80-
]}]
85+
]}];
86+
(_Else) ->
87+
[{c, []}]
8188
end, ActRec)
8289
}
8390
],

src/lorawan_application_handler.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
%
66
-module(lorawan_application_handler).
77

8-
-export([init/0, handle_join/3, handle_rx/4]).
8+
-export([init/0, handle_join/3, handle_rx/5]).
99
-export([store_frame/2, send_stored_frames/2]).
1010

1111
-define(MAX_DELAY, 250). % milliseconds
@@ -33,8 +33,8 @@ invoke_init({App, Module}) when is_atom(Module) ->
3333
handle_join(DevAddr, App, AppArgs) ->
3434
invoke_handler(handle_join, App, [DevAddr, App, AppArgs]).
3535

36-
handle_rx(DevAddr, App, AppArgs, RxData) ->
37-
invoke_handler(handle_rx, App, [DevAddr, App, AppArgs, RxData]).
36+
handle_rx(DevAddr, App, AppArgs, RxData, RxQ) ->
37+
invoke_handler(handle_rx, App, [DevAddr, App, AppArgs, RxData, RxQ]).
3838

3939
invoke_handler(Fun, App, Params) ->
4040
{ok, Modules} = application:get_env(plugins),

src/lorawan_application_microchip_mote.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
-module(lorawan_application_microchip_mote).
1010
-behaviour(lorawan_application).
1111

12-
-export([init/1, handle_join/3, handle_rx/4]).
12+
-export([init/1, handle_join/3, handle_rx/5]).
1313

1414
-include_lib("lorawan_server_api/include/lorawan_application.hrl").
1515

@@ -22,14 +22,14 @@ handle_join(_DevAddr, _App, _AppArgs) ->
2222

2323
% the data structure is explained in
2424
% Lora_Legacy_Mote_Firmware/Includes/Board/MOTEapp.c:520
25-
handle_rx(DevAddr, _App, _AppArgs, #rxdata{data= <<Light:5/binary, Temp:3/binary>>}) ->
25+
handle_rx(DevAddr, _App, _AppArgs, #rxdata{data= <<Light:5/binary, Temp:3/binary>>}, _RxQ) ->
2626
lager:debug("PUSH_DATA ~s ~s",[DevAddr, Light, Temp]),
2727
% display actual time
2828
{H, M, S} = time(),
2929
Time = lists:flatten(io_lib:format('~2..0b:~2..0b:~2..0b', [H, M, S])),
3030
{send, #txdata{port=2, data=list_to_binary(Time)}};
3131

32-
handle_rx(_DevAddr, _App, _AppArgs, RxData) ->
32+
handle_rx(_DevAddr, _App, _AppArgs, RxData, _RxQ) ->
3333
{error, {unexpected_data, RxData}}.
3434

3535
% end of file

src/lorawan_application_semtech_mote.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
-module(lorawan_application_semtech_mote).
1010
-behaviour(lorawan_application).
1111

12-
-export([init/1, handle_join/3, handle_rx/4]).
12+
-export([init/1, handle_join/3, handle_rx/5]).
1313

1414
-include_lib("lorawan_server_api/include/lorawan_application.hrl").
1515

@@ -22,12 +22,12 @@ handle_join(_DevAddr, _App, _AppArgs) ->
2222

2323
% the data structure is explained in
2424
% https://github.com/Lora-net/LoRaMac-node/blob/master/src/apps/LoRaMac/classA/LoRaMote/main.c#L207
25-
handle_rx(DevAddr, _App, _AppArgs, #rxdata{port=2, data= <<LED, Press:16, Temp:16, _AltBar:16, Batt, _Lat:24, _Lon:24, _AltGps:16>>}) ->
25+
handle_rx(DevAddr, _App, _AppArgs, #rxdata{port=2, data= <<LED, Press:16, Temp:16, _AltBar:16, Batt, _Lat:24, _Lon:24, _AltGps:16>>}, _RxQ) ->
2626
lager:debug("PUSH_DATA ~w ~w ~w ~w",[DevAddr, Press, Temp, Batt]),
2727
% blink with the LED indicator
2828
{send, #txdata{port=2, data= <<((LED+1) rem 2)>>}};
2929

30-
handle_rx(_DevAddr, _App, _AppArgs, RxData) ->
30+
handle_rx(_DevAddr, _App, _AppArgs, RxData, _RxQ) ->
3131
{error, {unexpected_data, RxData}}.
3232

3333
% end of file

src/lorawan_application_websocket.erl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
-module(lorawan_application_websocket).
77
-behaviour(lorawan_application).
88

9-
-export([init/1, handle_join/3, handle_rx/4]).
9+
-export([init/1, handle_join/3, handle_rx/5]).
1010

1111
-include_lib("lorawan_server_api/include/lorawan_application.hrl").
1212

@@ -20,15 +20,15 @@ handle_join(_DevAddr, _App, _AppArgs) ->
2020
% accept any device
2121
ok.
2222

23-
handle_rx(DevAddr, _App, AppArgs, #rxdata{last_lost=true} = RxData) ->
24-
send_to_sockets(DevAddr, AppArgs, RxData),
23+
handle_rx(DevAddr, _App, AppArgs, #rxdata{last_lost=true} = RxData, RxQ) ->
24+
send_to_sockets(DevAddr, AppArgs, RxData, RxQ),
2525
retransmit;
26-
handle_rx(DevAddr, _App, AppArgs, #rxdata{port=Port} = RxData) ->
27-
send_to_sockets(DevAddr, AppArgs, RxData),
26+
handle_rx(DevAddr, _App, AppArgs, #rxdata{port=Port} = RxData, RxQ) ->
27+
send_to_sockets(DevAddr, AppArgs, RxData, RxQ),
2828
lorawan_application_handler:send_stored_frames(DevAddr, Port).
2929

30-
send_to_sockets(DevAddr, AppArgs, RxData) ->
30+
send_to_sockets(DevAddr, AppArgs, RxData, RxQ) ->
3131
Sockets = lorawan_ws_frames:get_processes(DevAddr, AppArgs),
32-
[Pid ! {send, DevAddr, AppArgs, RxData} || Pid <- Sockets].
32+
[Pid ! {send, DevAddr, AppArgs, RxData, RxQ} || Pid <- Sockets].
3333

3434
% end of file

src/lorawan_handler.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
%
2-
% Copyright (c) 2016 Petr Gotthard <[email protected]>
2+
% Copyright (c) 2016-2017 Petr Gotthard <[email protected]>
33
% All rights reserved.
44
% Distributed under the terms of the MIT License. See the LICENSE file.
55
%
66
-module(lorawan_handler).
77

88
-export([handle_rxpk/3]).
9-
-export([rxpk/4]).
9+
-export([rxpk/3]).
1010

1111
handle_rxpk(MAC, RxQ, PHYPayload) ->
12-
Pid = spawn_link(?MODULE, rxpk, [self(), MAC, RxQ, PHYPayload]),
12+
Pid = spawn_link(?MODULE, rxpk, [MAC, RxQ, PHYPayload]),
1313
{ok, Pid}.
1414

15-
rxpk(Caller, MAC, RxQ, PHYPayload) ->
15+
rxpk(MAC, RxQ, PHYPayload) ->
1616
case lorawan_mac:process_frame(MAC, RxQ, PHYPayload) of
1717
ok -> ok;
1818
{send, Gateway, TxQ, PHYPayload2} ->
19-
lorawan_iface_forwarder:txsend(Caller, Gateway, TxQ, PHYPayload2);
19+
lorawan_iface_forwarder:txsend(Gateway, TxQ, PHYPayload2);
2020
{error, Error} ->
2121
lager:error("ERROR: ~w", [Error])
2222
end.

src/lorawan_iface_forwarder.erl

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
%
2-
% Copyright (c) 2016 Petr Gotthard <[email protected]>
2+
% Copyright (c) 2016-2017 Petr Gotthard <[email protected]>
33
% All rights reserved.
44
% Distributed under the terms of the MIT License. See the LICENSE file.
55
%
@@ -12,7 +12,7 @@
1212

1313
-export([start_link/1]).
1414
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
15-
-export([txsend/4]).
15+
-export([txsend/3]).
1616

1717
-include_lib("lorawan_server_api/include/lorawan_application.hrl").
1818
-include("lorawan.hrl").
@@ -23,9 +23,14 @@ start_link(Port) ->
2323
gen_server:start_link({global, ?MODULE}, ?MODULE, [Port], []).
2424

2525
init([Port]) ->
26-
process_flag(trap_exit, true),
27-
{ok, Socket} = gen_udp:open(Port, [binary]),
28-
{ok, #state{sock=Socket, pulladdr=dict:new()}}.
26+
case gen_udp:open(Port, [binary]) of
27+
{ok, Socket} ->
28+
process_flag(trap_exit, true),
29+
{ok, #state{sock=Socket, pulladdr=dict:new()}};
30+
{error, Reason} ->
31+
lager:error("Failed to start the packet_forwarder interface: ~w", Reason),
32+
{stop, Reason}
33+
end.
2934

3035
handle_call(_Request, _From, State) ->
3136
{stop, {error, unknownmsg}, State}.
@@ -137,22 +142,30 @@ status(MAC, Pk) ->
137142
lager:error("ERROR: ~w", [Error])
138143
end.
139144

140-
txsend(Pid, Gateway, TxQ, PHYPayload) ->
145+
txsend(Gateway, TxQ, PHYPayload) ->
141146
% TX only supported on radio A
142-
Pk = [{txpk, build_txpk(TxQ#txq{rfch=Gateway#gateway.tx_rfch}, PHYPayload)}],
147+
Pk = [{txpk, build_txpk(TxQ, Gateway#gateway.tx_rfch, PHYPayload)}],
143148
% lager:debug("<--- ~w", [Pk]),
144-
gen_server:cast(Pid, {send, Gateway#gateway.mac, jsx:encode(Pk)}).
149+
gen_server:cast({global, ?MODULE}, {send, Gateway#gateway.mac, jsx:encode(Pk)}).
145150

146151
parse_rxpk(Pk) ->
147152
Data = base64:decode(proplists:get_value(data, Pk)),
148153
case proplists:get_value(modu, Pk) of
149154
<<"LORA">> ->
150-
RxQ = ?to_record(rxq, Pk),
151-
{RxQ#rxq{erlst=erlang:monotonic_time(milli_seconds)}, Data}
155+
RxQ = list_to_tuple([rxq|[get_rxpk_field(X, Pk) || X <- record_info(fields, rxq)]]),
156+
{RxQ#rxq{srvtmst=erlang:monotonic_time(milli_seconds)}, Data}
152157
end.
153158

154-
build_txpk(TxQ, Data) ->
159+
get_rxpk_field(time, List) ->
160+
case proplists:get_value(time, List) of
161+
undefined -> undefined;
162+
Value -> iso8601:parse_exact(Value)
163+
end;
164+
get_rxpk_field(Field, List) ->
165+
proplists:get_value(Field, List).
166+
167+
build_txpk(TxQ, RFch, Data) ->
155168
?to_proplist(txq, TxQ) ++
156-
[{imme, false}, {modu, <<"LORA">>}, {ipol, true}, {size, byte_size(Data)}, {data, base64:encode(Data)}].
169+
[{imme, false}, {modu, <<"LORA">>}, {rfch, RFch}, {ipol, true}, {size, byte_size(Data)}, {data, base64:encode(Data)}].
157170

158171
% end of file

src/lorawan_mac.erl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,9 @@ reset_link(DevAddr) ->
231231
store_adr(Link, ADR) -> Link#link{adr_flag_use=ADR}.
232232

233233
store_rxpk(Gateway, Link, RxQ, Frame) ->
234-
% store #rxframe{frid, mac, rssi, lsnr, freq, datr, codr, devaddr, fcnt, port, data, region, datetime, devstat}
234+
% store #rxframe{frid, mac, rxq, devaddr, fcnt, port, data, region, datetime, devstat}
235235
mnesia:dirty_write(rxframes, #rxframe{frid= <<(erlang:system_time()):64>>,
236-
mac=Gateway#gateway.mac, rssi=RxQ#rxq.rssi, lsnr=RxQ#rxq.lsnr, freq=RxQ#rxq.freq,
237-
datr=RxQ#rxq.datr, codr=RxQ#rxq.codr, devaddr=Link#link.devaddr,
236+
mac=Gateway#gateway.mac, rxq=RxQ, devaddr=Link#link.devaddr,
238237
fcnt=Link#link.fcntup, port=Frame#frame.fport, data=Frame#frame.data,
239238
region=Link#link.region, datetime=calendar:universal_time(), devstat=Link#link.devstat}).
240239

@@ -260,7 +259,7 @@ handle_rxpk(Gateway, RxQ, MType, Link, Fresh, Frame)
260259
end.
261260

262261
handle_uplink(Gateway, RxQ, Confirm, Link, #frame{devaddr=DevAddr,
263-
adr_ack_req=ADRACKReq, ack=ACK, fport=FPort, fopts=FOpts, data=RxData}=Frame) ->
262+
adr_ack_req=ADRACKReq, ack=ACK, fcnt=FCnt, fport=FPort, fopts=FOpts, data=RxData}=Frame) ->
264263
{ok, L2, FOptsOut} = lorawan_mac_commands:handle(Link, FOpts),
265264
ok = mnesia:dirty_write(links, L2#link{last_rx=calendar:universal_time()}),
266265
ok = store_rxpk(Gateway, L2, RxQ, Frame),
@@ -284,7 +283,7 @@ handle_uplink(Gateway, RxQ, Confirm, Link, #frame{devaddr=DevAddr,
284283
end,
285284
% invoke applications
286285
case lorawan_application_handler:handle_rx(Link#link.devaddr, Link#link.app, Link#link.appid,
287-
#rxdata{port=FPort, data=RxData, last_lost=LastLost, shall_reply=ShallReply}) of
286+
#rxdata{fcnt=FCnt, port=FPort, data=RxData, last_lost=LastLost, shall_reply=ShallReply}, RxQ) of
288287
retransmit ->
289288
{send, Gateway, choose_tx(Link#link.region, RxQ), LostFrame};
290289
{send, TxData} ->
@@ -300,7 +299,7 @@ choose_tx(Region, RxQ) ->
300299
Rx1Delay = lorawan_mac_region:regional_config(rx1_delay, Region) / 1000,
301300
{ok, GwDelay} = application:get_env(gateway_delay),
302301
% transmit as soon as possible
303-
case erlang:monotonic_time(milli_seconds) - RxQ#rxq.erlst of
302+
case erlang:monotonic_time(milli_seconds) - RxQ#rxq.srvtmst of
304303
Small when Small < Rx1Delay - GwDelay ->
305304
lorawan_mac_region:rx1_rf(Region, RxQ, rx1_delay);
306305
_Big ->

src/lorawan_mac_region.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
-export([rx1_rf/3, rx2_rf/3, rx2_dr/1, default_adr/1]).
99
-export([datar_to_dr/2, freq_range/1, regional_config/2]).
1010

11-
-include("lorawan.hrl").
11+
-include_lib("lorawan_server_api/include/lorawan_application.hrl").
1212

1313
% we calculate in fixed-point numbers
1414
rx1_rf(Region, RxQ, Window)

src/lorawan_ws_frames.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ store_frame0(DevAddr, TxData, State) ->
8484
lorawan_application_handler:store_frame(DevAddr, TxData),
8585
{ok, State}.
8686

87-
websocket_info({send, _DevAddr, _AppArgs, #rxdata{data=Data}}, #state{format=raw} = State) ->
87+
websocket_info({send, _DevAddr, _AppArgs, #rxdata{data=Data}, _RxQ}, #state{format=raw} = State) ->
8888
{reply, {binary, Data}, State};
89-
websocket_info({send, DevAddr, _AppArgs, #rxdata{port=Port, data=Data}}, #state{format=json} = State) ->
90-
Msg = [{devaddr, lorawan_mac:binary_to_hex(DevAddr)}, {port, Port}, {data, lorawan_mac:binary_to_hex(Data)}],
89+
websocket_info({send, DevAddr, _AppArgs, RxData, RxQ}, #state{format=json} = State) ->
90+
Msg = lorawan_admin:build_admin([{devaddr, DevAddr}, {rxq, RxQ} | ?to_proplist(rxdata, RxData)]),
9191
{reply, {text, jsx:encode(Msg)}, State};
9292
websocket_info(Info, State) ->
9393
lager:warning("Unknown info ~w", [Info]),

0 commit comments

Comments
 (0)