282 lines
9.3 KiB
Erlang
282 lines
9.3 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%
|
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
%% you may not use this file except in compliance with the License.
|
|
%% You may obtain a copy of the License at
|
|
%%
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
%%
|
|
%% Unless required by applicable law or agreed to in writing, software
|
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
%% See the License for the specific language governing permissions and
|
|
%% limitations under the License.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_lwm2m_api).
|
|
|
|
-behaviour(minirest_api).
|
|
|
|
-include_lib("hocon/include/hoconsc.hrl").
|
|
-include_lib("typerefl/include/types.hrl").
|
|
|
|
-export([api_spec/0, paths/0, schema/1, fields/1, namespace/0]).
|
|
|
|
-export([lookup/2, observe/2, read/2, write/2]).
|
|
|
|
-define(PATH(Suffix), "/gateways/lwm2m/clients/:clientid" Suffix).
|
|
-define(DATA_TYPE, ['Integer', 'Float', 'Time', 'String', 'Boolean', 'Opaque', 'Objlnk']).
|
|
-define(TAGS, [<<"LwM2M Gateways">>]).
|
|
|
|
-import(hoconsc, [mk/2, ref/1, ref/2]).
|
|
-import(emqx_dashboard_swagger, [error_codes/2]).
|
|
|
|
-elvis([{elvis_style, atom_naming_convention, disable}]).
|
|
|
|
namespace() -> "lwm2m".
|
|
|
|
api_spec() ->
|
|
emqx_dashboard_swagger:spec(?MODULE).
|
|
|
|
paths() ->
|
|
[
|
|
?PATH("/lookup"), ?PATH("/observe"), ?PATH("/read"), ?PATH("/write")
|
|
].
|
|
|
|
schema(?PATH("/lookup")) ->
|
|
#{
|
|
'operationId' => lookup,
|
|
get => #{
|
|
tags => ?TAGS,
|
|
desc => ?DESC(lookup_resource),
|
|
summary => <<"List Client's Resources">>,
|
|
parameters => [
|
|
{clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})},
|
|
{path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})},
|
|
{action, mk(binary(), #{in => query, required => true, example => "discover"})}
|
|
],
|
|
'requestBody' => [],
|
|
responses => #{
|
|
200 => [
|
|
{clientid, mk(binary(), #{example => "urn:oma:lwm2m:oma:2"})},
|
|
{path, mk(binary(), #{example => "/3/0/7"})},
|
|
{action, mk(binary(), #{example => "discover"})},
|
|
{'codeMsg', mk(binary(), #{example => "reply_not_received"})},
|
|
{content, mk(hoconsc:array(ref(resource)), #{})}
|
|
],
|
|
404 => error_codes(['CLIENT_NOT_FOUND'], <<"Client not found">>)
|
|
}
|
|
}
|
|
};
|
|
schema(?PATH("/observe")) ->
|
|
#{
|
|
'operationId' => observe,
|
|
post => #{
|
|
tags => ?TAGS,
|
|
desc => ?DESC(observe_resource),
|
|
summary => <<"Observe a Resource">>,
|
|
parameters => [
|
|
{clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})},
|
|
{path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})},
|
|
{enable, mk(boolean(), #{in => query, required => true, example => true})}
|
|
],
|
|
'requestBody' => [],
|
|
responses => #{
|
|
204 => <<"No Content">>,
|
|
404 => error_codes(['CLIENT_NOT_FOUND'], <<"Clientid not found">>)
|
|
}
|
|
}
|
|
};
|
|
schema(?PATH("/read")) ->
|
|
#{
|
|
'operationId' => read,
|
|
post => #{
|
|
tags => ?TAGS,
|
|
desc => ?DESC(read_resource),
|
|
summary => <<"Read Value from a Resource Path">>,
|
|
parameters => [
|
|
{clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})},
|
|
{path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})}
|
|
],
|
|
responses => #{
|
|
204 => <<"No Content">>,
|
|
404 => error_codes(['CLIENT_NOT_FOUND'], <<"clientid not found">>)
|
|
}
|
|
}
|
|
};
|
|
schema(?PATH("/write")) ->
|
|
#{
|
|
'operationId' => write,
|
|
post => #{
|
|
tags => ?TAGS,
|
|
desc => ?DESC(write_resource),
|
|
summary => <<"Write a Value to Resource Path">>,
|
|
parameters => [
|
|
{clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})},
|
|
{path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})},
|
|
{type,
|
|
mk(
|
|
hoconsc:enum(?DATA_TYPE),
|
|
#{in => query, required => true, example => 'Integer'}
|
|
)},
|
|
{value, mk(binary(), #{in => query, required => true, example => 123})}
|
|
],
|
|
responses => #{
|
|
204 => <<"No Content">>,
|
|
404 => error_codes(['CLIENT_NOT_FOUND'], <<"Clientid not found">>)
|
|
}
|
|
}
|
|
}.
|
|
|
|
fields(resource) ->
|
|
[
|
|
{operations, mk(binary(), #{desc => ?DESC(operations), example => "E"})},
|
|
{'dataType',
|
|
mk(hoconsc:enum(?DATA_TYPE), #{
|
|
desc => ?DESC(dataType),
|
|
example => 'Integer'
|
|
})},
|
|
{path, mk(binary(), #{desc => ?DESC(path), example => "urn:oma:lwm2m:oma:2"})},
|
|
{name, mk(binary(), #{desc => ?DESC(name), example => "lwm2m-test"})}
|
|
].
|
|
|
|
lookup(get, #{bindings := Bindings, query_string := QS}) ->
|
|
ClientId = maps:get(clientid, Bindings),
|
|
case emqx_gateway_cm_registry:lookup_channels(lwm2m, ClientId) of
|
|
[Channel | _] ->
|
|
#{
|
|
<<"path">> := Path,
|
|
<<"action">> := Action
|
|
} = QS,
|
|
{ok, Result} = emqx_lwm2m_channel:lookup_cmd(Channel, Path, Action),
|
|
lookup_return(Result, ClientId, Action, Path);
|
|
_ ->
|
|
{404, #{code => 'CLIENT_NOT_FOUND'}}
|
|
end.
|
|
|
|
lookup_return(undefined, ClientId, Action, Path) ->
|
|
{200, #{
|
|
clientid => ClientId,
|
|
action => Action,
|
|
code => <<"6.01">>,
|
|
codeMsg => <<"reply_not_received">>,
|
|
path => Path
|
|
}};
|
|
lookup_return({Code, CodeMsg, Content}, ClientId, Action, Path) ->
|
|
{200,
|
|
format_cmd_content(
|
|
Content,
|
|
Action,
|
|
#{
|
|
clientid => ClientId,
|
|
action => Action,
|
|
code => Code,
|
|
codeMsg => CodeMsg,
|
|
path => Path
|
|
}
|
|
)}.
|
|
|
|
format_cmd_content(undefined, _MsgType, Result) ->
|
|
Result;
|
|
format_cmd_content(Content, <<"discover">>, Result) ->
|
|
[H | Content1] = Content,
|
|
{_, [HObjId]} = emqx_lwm2m_session:parse_object_list(H),
|
|
[ObjId | _] = path_list(HObjId),
|
|
ObjectList =
|
|
case Content1 of
|
|
[Content2 | _] ->
|
|
{_, ObjL} = emqx_lwm2m_session:parse_object_list(Content2),
|
|
ObjL;
|
|
[] ->
|
|
[]
|
|
end,
|
|
|
|
R =
|
|
case emqx_lwm2m_xml_object:get_obj_def(binary_to_integer(ObjId), true) of
|
|
{error, _} ->
|
|
lists:map(fun(Object) -> #{Object => Object} end, ObjectList);
|
|
ObjDefinition ->
|
|
lists:map(fun(Obj) -> to_operations(Obj, ObjDefinition) end, ObjectList)
|
|
end,
|
|
Result#{content => R};
|
|
format_cmd_content(Content, _, Result) ->
|
|
Result#{content => Content}.
|
|
|
|
to_operations(Obj, ObjDefinition) ->
|
|
[_, _, RawResId | _] = path_list(Obj),
|
|
ResId = binary_to_integer(RawResId),
|
|
Operations =
|
|
case emqx_lwm2m_xml_object:get_resource_operations(ResId, ObjDefinition) of
|
|
"E" ->
|
|
#{operations => <<"E">>};
|
|
Oper ->
|
|
#{
|
|
'dataType' =>
|
|
list_to_binary(
|
|
emqx_lwm2m_xml_object:get_resource_type(ResId, ObjDefinition)
|
|
),
|
|
operations => list_to_binary(Oper)
|
|
}
|
|
end,
|
|
Operations#{
|
|
path => Obj,
|
|
name => list_to_binary(emqx_lwm2m_xml_object:get_resource_name(ResId, ObjDefinition))
|
|
}.
|
|
|
|
path_list(Path) ->
|
|
case binary:split(emqx_utils_binary:trim(Path, $/), [<<$/>>], [global]) of
|
|
[ObjId, ObjInsId, ResId, ResInstId] -> [ObjId, ObjInsId, ResId, ResInstId];
|
|
[ObjId, ObjInsId, ResId] -> [ObjId, ObjInsId, ResId];
|
|
[ObjId, ObjInsId] -> [ObjId, ObjInsId];
|
|
[ObjId] -> [ObjId]
|
|
end.
|
|
|
|
observe(post, #{
|
|
bindings := #{clientid := ClientId},
|
|
query_string := #{<<"path">> := Path, <<"enable">> := Enable}
|
|
}) ->
|
|
MsgType =
|
|
case Enable of
|
|
true -> <<"observe">>;
|
|
_ -> <<"cancel-observe">>
|
|
end,
|
|
|
|
Cmd = #{
|
|
<<"msgType">> => MsgType,
|
|
<<"data">> => #{<<"path">> => Path}
|
|
},
|
|
|
|
send_cmd(ClientId, Cmd).
|
|
|
|
read(post, #{
|
|
bindings := #{clientid := ClientId},
|
|
query_string := Qs
|
|
}) ->
|
|
Cmd = #{
|
|
<<"msgType">> => <<"read">>,
|
|
<<"data">> => Qs
|
|
},
|
|
|
|
send_cmd(ClientId, Cmd).
|
|
|
|
write(post, #{
|
|
bindings := #{clientid := ClientId},
|
|
query_string := Qs
|
|
}) ->
|
|
Cmd = #{
|
|
<<"msgType">> => <<"write">>,
|
|
<<"data">> => Qs
|
|
},
|
|
|
|
send_cmd(ClientId, Cmd).
|
|
|
|
send_cmd(ClientId, Cmd) ->
|
|
case emqx_gateway_cm_registry:lookup_channels(lwm2m, ClientId) of
|
|
[Channel | _] ->
|
|
ok = emqx_lwm2m_channel:send_cmd(Channel, Cmd),
|
|
{204};
|
|
_ ->
|
|
{404, #{code => 'CLIENT_NOT_FOUND'}}
|
|
end.
|