I developed a TCP server in the Phoenixframwork by using an implementation of the Erlang :gen_tcp module.
I can start the server by calling :gen_tcp.listen(port)
which then listens for new connections on this port.
One client is an automated picking system for pharmacies (basically an automated drug dispense robot).
So as a tcp client the robot is able to open a connection to my tcp server. The Server listens for new messages by the robot via the handle_info
-callback method and is also able to respond to the client within this request (:gen_tcp.send
).
The problem I am facing is that I have no idea how I would use this connection and send data back to the robot without a client request.
Since the robot is a tcp client (the company behind the robot says that there is currently no way that the robot could act as a server) there is no open port / robot server address I could send messages to. So I have to use the already established connection initialized by the client.
Setup
pharmacy_ui > pharmacy_api (Phoenix) > robot (vendor software)
Workflow:
- robot initializes a connection to api via tcp
- robot sends status information to api and gets a response
- at some point (see update 1), the api has to send a dispense request to the robot (by using the connection initialized in #1)
Step 1 and 2 work, part 3. doesn't.
This looks like a rather simple problem about tcp connections in Elixir/Phoenix, but any hint in the right direction is highly appreciated :)
So far I came up with this implementation (based on this blog post):
defmodule MyApi.TcpServerClean do
use GenServer
defmodule State do
defstruct port: nil, lsock: nil, request_count: 0
end
def start_link(port) do
:gen_server.start_link({ :local, :my_api }, __MODULE__, port, [])
end
def start_link() do
start_link 9876 # Default Port if non provided at startup
end
def get_count() do # test call from my_frontend
:gen_server.call(:my_api, :get_count)
end
def stop() do
:gen_server.cast(:my_api, :stop)
end
def init (port) do
{ :ok, lsock } = :gen_tcp.listen(port, [{ :active, true }])
{ :ok, %State{lsock: lsock, port: port}, 0 }
end
def handle_call(:get_count, _from, state) do
{ :reply, { :ok, state.request_count }, state }
end
def handle_cast(:stop , state) do
{ :noreply, state }
end
# handles client tcp requests
def handle_info({ :tcp, socket, raw_data}, state) do
do_rpc(socket, raw_data) # raw_data = data from robot
{ :noreply, %{ state | request_count: state.request_count + 1 } } # count for testing states
end
def handle_info(:timeout, state) do
{ :ok, _sock } = :gen_tcp.accept state.lsock
{ :noreply, state }
end
def handle_info(:tcp_closed, state) do
# do something
{ :noreply, state }
end
def do_rpc(socket, raw_data) do
try do
# process data from robot and do something with it
resp = "My tcp server response ..." # test
:gen_tcp.send(socket, :io_lib.fwrite(resp, []))
catch
error -> :gen_tcp.send(socket, :io_lib.fwrite("~p~n", [error]))
end
end
end
Update 1:
At some point = A user (e.g. pharmacist) places an order at the ui frontend. Frontend triggers a post to the api and the api handles the post in the OrderController. OrderController has to transform the order (so that robot understands it) and passes it to the TcpServer which holds the connection to the robot. This workflow will happen many times per day.