Elixir Cowboy Websocket - 위도 경도 근접성으로 IOT 장치 쿼리
알려진 위도와 경도 위치를 가진 많은 장치는 위치의 변화를 지속적으로 알려줍니다.
우리의 목표는 주어진 위치 근처에 있는 장치에 메시지를 보내는 것입니다.
또는 한 장치가 주어진 반경 내에서 가까운 장치가 누구인지 알아야 하는 경우 또 다른 용도가 될 것입니다.
가능한 사용 사례
근처에 연결된 장치에서 메시지를 보내거나 가져옵니다.
많은 사용 사례에 적용할 수 있습니다.
택시 차량
택시 함대에서 새로운 승객이 여행을 요청한다고 가정하고,
그들은 우리에게 픽업 지점을 제공하고 시스템은 가장 가까운 택시로의 여행을 제공해야 합니다. 그런 다음 주변 지역의 택시에만 방송하면 됩니다.
센서 메쉬
센서 메시에서 장치 중 하나가 비정상적인 강의 값을 읽고 주어진 반경 내 주변 장치의 값을 알고 싶어합니다.
배달 음식
배송업체 오토바이 한 대가 타이어가 부러져
그런 다음 가까운 파트너에게 도움을 요청하여 음식을 분배하십시오.
목차
종속성
Showing the dependencies is the classic way to start an Elixir post.
defp deps do
[
{:cowboy, "~> 2.4"},
{:plug_cowboy, "~> 2.0"},
{:jason, "~> 1.3"},
{:geocoder, "~> 1.1"},
{:geocalc, "~> 0.8"}
]
end
응용 프로그램
The application is the core where all the involved processes will be started.
defmodule IotDevicesApp do
use Application
def start(_type, _args) do
children = [
Plug.Cowboy.child_spec(
scheme: :http,
plug: SocketApp.HttpRouter,
options: [dispatch: dispatch(),port: 5000]
),
Registry.child_spec(
keys: :duplicate,
name: Registry.IotApp
)
]
opts = [strategy: :one_for_one, name: IotDevicesApp.Application]
Supervisor.start_link(children, opts)
end
defp dispatch do
[
{:_,
[
{"/ws/[...]", IotDevicesApp.SocketHandlerLocation, []},
{:_, Plug.Cowboy.Handler, {SocketApp.HttpRouter, []}}
]
}
]
end
end
공간 구역화
In our app we have thousand of devices spread along a State, County or City, so measure the distance with all the device is not performant, a coarse zonification is nedeed and then apply a fine graded filtering using distance.
First solution that come to mind is a zonificate by city, using a reverse geocoding service likeGoogle Maps
또는 OpenStreetMap
지오코더 라이브러리는 우리의 필요에 맞습니다.
Hexdocs
Github
def getZone({lon, lat}) do
{:ok, coord} = Geocoder.call({lat, lon}, provider: Geocoder.Providers.OpenStreetMaps)
# OpenStreetMaps is only for testing, it is limited to 1 request by second.
"#{coord.location.state}, #{coord.location.city}"
end
기능 테스트:
times_square = {-73.985130,40.758896}
getZone(times_square)
Return : "New York, New York"
거리로 필터링
A distance function must to be implemented, some question are nedeed:
- How far away are the neighbors ? 1km, 5km, 10km
- Which level of accuracy we need 1m, 100m, 500m
- Need for speed ?
A) The fast way work in flat earth calculating the "Euclidean distance" in degrees, 1 degrees at equator 69 miles
def distance({cx, cy}, {nx, ny}) do
:math.sqrt(:math.pow(abs(cx - nx), 2) + :math.pow(abs(cy - ny), 2))
end
B) 많은 미적분학이 관련되어 있기 때문에 정확하고 약간 느릴 수 있습니다. GeoCalc에서 'haversine' 공식을 적용하십시오.
def distance({cx, cy}, {nx, ny}) do
Geocalc.distance_between([cx, cy], [nx, ny])
end
장치 레지스트리
It is necessary to store an keep a record ot all the connected devices, the right place is a Registry working as publisher subscriber. The PID and the location of every new connection is stored in the Registry, it will be showed bellow, in the Socket Handler.
Elixir Registry레지스트리는 ETS(Erlang Term Storage)를 기반으로 합니다.
Erlang Term Storage
소켓 핸들러
The start point to understand socket handler is the documentation: Cowboy WebSocketdefmodule IotDevicesApp.SocketHandlerLocation do
@behaviour :cowboy_websocket
@degrees 0.5
@meters 1500
# When a client open the connection, it send to us his latitude and longitude
# javascript: websocket = new WebSocket("ws://192.168.1.109:5000/ws?lon=-73.985130&lat=40.758896");
def init(request, _state) do
vars = URI.query_decoder(request.qs) |> Map.new()
lon_float = String.to_float(Map.get(vars, "lon", 0.0))
lat_float = String.to_float(Map.get(vars, "lat", 0.0))
{:cowboy_websocket, request, {lon_float, lat_float}, %{idle_timeout: :infinity}}
end
# Called once the connection has been upgraded to Websocket.
def websocket_init(location) do
IO.puts("-------------websocket_init--------------")
# this function have different PID that the init() function
# for that reason the PID registration is here
key = getZone(location)
Registry.IotApp
|> Registry.register(key, location)
{:ok, {key, location}}
end
# websocket_handle called when:
# javascript: websocket.send(JSON.stringify({action:"updlocation", lon:11, lat:22})
# javascript: websocket.send(JSON.stringify({action:"status", ...status})
# javascript: websocket.send(JSON.stringify({action:"wakeupneighbors"})
def websocket_handle({:text, json}, state = {key, location}) do
IO.puts("-------------websocket_handle--------------")
IO.inspect(state, label: "state")
payload = Jason.decode!(json)
case payload["action"] do
"status" -> {:reply, reportStatus(payload["status"]), state}
"updlocation" -> updateLocation(self(), key, {payload["lon"], payload["lat"]})
"wakeupneighbors" -> {:reply, wakeupNeighbors(key, location), state}
_ -> {:ok, state}
end
end
# Here broadcast happen
# websocket_info: Will be called for every Erlang message received.
# Trigger from wakeupNeighbors()
def websocket_info(info, state) do
{:reply, {:text, info}, state}
end
#----------- Next are utilities functions ---------------------
def reportStatus(status) do
IO.puts("-------------reportStatus--------------")
IO.inspect(status ,label: "status")
# Here put your logic
{:ok, str} = Jason.encode(%{"action"=>"ack"})
{:text, str}
end
def updateLocation(pid, oldkey, newlocation) do
IO.puts("-------------updateLocation--------------")
IO.inspect({pid, self()}, label: "PID")
# this function have same PID that the websocket_init() function
# IO.inspect(newlocation ,label: "newlocation")
newkey = getZone(newlocation)
IO.inspect(Registry.values(Registry.IotApp, oldkey, self()), label: "old location")
Registry.unregister(Registry.IotApp, oldkey)
Registry.register(Registry.IotApp, newkey, newlocation)
IO.inspect(Registry.values(Registry.IotApp, newkey, self()), label: "new location")
{:ok, str} = Jason.encode(%{"action" => "ack"})
{:reply, {:text, str}, {newkey, newlocation}}
end
def wakeupNeighbors(key, location) do
# this function have same PID that the websocket_init() function
IO.puts("-------------wakeupNeighbors--------------")
{:ok, message} = Jason.encode(%{"action" => "report_status"})
Registry.IotApp
|> Registry.dispatch(key, fn entries ->
for {pid, neighbor} <- entries do
IO.inspect(neighbor, label: "neighbor location")
if pid != self() && isNeighbour(location, neighbor) do
IO.inspect(pid, label: " send msg to")
Process.send(pid, message, [])
end
end
end)
{:ok, str} = Jason.encode(%{"action" => "ack"})
{:text, str}
end
def isNeighbour(location, neighbor) do
# @degrees > distanceEuclidean(location,neighbor)
@meters > distanceHaversine(location, neighbor)
end
def distanceEuclidean({cx, cy}, {nx, ny}) do
:math.sqrt(:math.pow(abs(cx - nx), 2) + :math.pow(abs(cy - ny), 2))
end
def distanceHaversine({cx, cy}, {nx, ny}) do
Geocalc.distance_between([cx, cy], [nx, ny])
end
def getZone({lon, lat}) do
{:ok, coord} = Geocoder.call({lat, lon}, provider: Geocoder.Providers.OpenStreetMaps)
# OpenStreetMaps is only for testing, it is limited to 1 request by second.
key = "#{coord.location.state}, #{coord.location.city}"
IO.inspect(key, label: "getZone")
key
end
end
Reference
이 문제에 관하여(Elixir Cowboy Websocket - 위도 경도 근접성으로 IOT 장치 쿼리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/lionelmarco/elixir-cowboy-websocket-querying-iot-devices-by-latitude-longitude-proximity-3mh1텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)