• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <iostream>
18 #include <fstream>
19 #include <memory>
20 #include <string>
21 
22 #include <grpcpp.h>
23 
24 #include "gnss_grpc_proxy.grpc.pb.h"
25 
26 #include <signal.h>
27 
28 #include <chrono>
29 #include <deque>
30 #include <mutex>
31 #include <thread>
32 #include <vector>
33 
34 #include <gflags/gflags.h>
35 #include <android-base/logging.h>
36 
37 #include <common/libs/fs/shared_fd.h>
38 #include <common/libs/fs/shared_buf.h>
39 #include <common/libs/fs/shared_select.h>
40 #include <host/libs/config/cuttlefish_config.h>
41 
42 using grpc::Server;
43 using grpc::ServerBuilder;
44 using grpc::ServerContext;
45 using grpc::Status;
46 using gnss_grpc_proxy::SendNmeaRequest;
47 using gnss_grpc_proxy::SendNmeaReply;
48 using gnss_grpc_proxy::GnssGrpcProxy;
49 
50 DEFINE_int32(gnss_in_fd,
51              -1,
52              "File descriptor for the gnss's input channel");
53 DEFINE_int32(gnss_out_fd,
54              -1,
55              "File descriptor for the gnss's output channel");
56 
57 DEFINE_int32(gnss_grpc_port,
58              -1,
59              "Service port for gnss grpc");
60 
61 DEFINE_string(gnss_file_path,
62               "",
63               "NMEA file path for gnss grpc");
64 
65 constexpr char CMD_GET_LOCATION[] = "CMD_GET_LOCATION";
66 constexpr uint32_t GNSS_SERIAL_BUFFER_SIZE = 4096;
67 // Logic and data behind the server's behavior.
68 class GnssGrpcProxyServiceImpl final : public GnssGrpcProxy::Service {
69   public:
GnssGrpcProxyServiceImpl(cuttlefish::SharedFD gnss_in,cuttlefish::SharedFD gnss_out)70     GnssGrpcProxyServiceImpl(cuttlefish::SharedFD gnss_in,
71                      cuttlefish::SharedFD gnss_out) : gnss_in_(gnss_in),
72                                                   gnss_out_(gnss_out) {}
SendNmea(ServerContext * context,const SendNmeaRequest * request,SendNmeaReply * reply)73     Status SendNmea(ServerContext* context, const SendNmeaRequest* request,
74                     SendNmeaReply* reply) override {
75       reply->set_reply("Received nmea record.");
76 
77       auto buffer = request->nmea();
78       std::lock_guard<std::mutex> lock(cached_nmea_mutex);
79       cached_nmea = request->nmea();
80       return Status::OK;
81     }
82 
sendToSerial()83     void sendToSerial() {
84       LOG(DEBUG) << "Send NMEA to serial:" << cached_nmea;
85       std::lock_guard<std::mutex> lock(cached_nmea_mutex);
86       ssize_t bytes_written = cuttlefish::WriteAll(gnss_in_, cached_nmea);
87       if (bytes_written < 0) {
88           LOG(ERROR) << "Error writing to fd: " << gnss_in_->StrError();
89       }
90     }
91 
StartServer()92     void StartServer() {
93       // Create a new thread to handle writes to the gnss and to the any client
94       // connected to the socket.
95       read_thread_ = std::thread([this]() { ReadLoop(); });
96     }
97 
StartReadFileThread()98     void StartReadFileThread() {
99       // Create a new thread to handle writes to the gnss and to the any client
100       // connected to the socket.
101       file_read_thread_ = std::thread([this]() { ReadNmeaFromLocalFile(); });
102     }
103 
ReadNmeaFromLocalFile()104     void ReadNmeaFromLocalFile() {
105       std::ifstream file(FLAGS_gnss_file_path);
106       if (file.is_open()) {
107           std::string line;
108           std::string lastLine;
109           int count = 0;
110           while (std::getline(file, line)) {
111               count++;
112               /* Only support a lite version of NEMA format to make it simple.
113                * Records will only contains $GPGGA, $GPRMC,
114                * $GPGGA,213204.00,3725.371240,N,12205.589239,W,7,,0.38,-26.75,M,0.0,M,0.0,0000*78
115                * $GPRMC,213204.00,A,3725.371240,N,12205.589239,W,000.0,000.0,290819,,,A*49
116                * $GPGGA,....
117                * $GPRMC,....
118                * Sending at 1Hz, currently user should
119                * provide a NMEA file that has one location per second. need some extra work
120                * to make it more generic, i.e. align with the timestamp in the file.
121                */
122               if (count % 2 == 0) {
123                 {
124                   std::lock_guard<std::mutex> lock(cached_nmea_mutex);
125                   cached_nmea = lastLine + '\n' + line;
126                 }
127                 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
128               }
129               lastLine = line;
130           }
131           file.close();
132       } else {
133         LOG(ERROR) << "Can not open NMEA file: " << FLAGS_gnss_file_path ;
134         return;
135       }
136     }
137   private:
ReadLoop()138     [[noreturn]] void ReadLoop() {
139       cuttlefish::SharedFDSet read_set;
140       read_set.Set(gnss_out_);
141       std::vector<char> buffer(GNSS_SERIAL_BUFFER_SIZE);
142       int total_read = 0;
143       std::string gnss_cmd_str;
144       int flags = gnss_out_->Fcntl(F_GETFL, 0);
145       gnss_out_->Fcntl(F_SETFL, flags | O_NONBLOCK);
146       while (true) {
147         auto bytes_read = gnss_out_->Read(buffer.data(), buffer.size());
148         if (bytes_read > 0) {
149           std::string s(buffer.data(), bytes_read);
150           gnss_cmd_str += s;
151           // In case random string sent though /dev/gnss0, gnss_cmd_str will auto resize,
152           // to get rid of first page.
153           if (gnss_cmd_str.size() > GNSS_SERIAL_BUFFER_SIZE * 2) {
154             gnss_cmd_str = gnss_cmd_str.substr(gnss_cmd_str.size() - GNSS_SERIAL_BUFFER_SIZE);
155           }
156           total_read += bytes_read;
157           if (gnss_cmd_str.find(CMD_GET_LOCATION) != std::string::npos) {
158             sendToSerial();
159             gnss_cmd_str = "";
160             total_read = 0;
161           }
162         } else {
163           if (gnss_out_->GetErrno() == EAGAIN|| gnss_out_->GetErrno() == EWOULDBLOCK) {
164             std::this_thread::sleep_for(std::chrono::milliseconds(100));
165           } else {
166             LOG(ERROR) << "Error reading fd " << FLAGS_gnss_out_fd << ": "
167               << " Error code: " << gnss_out_->GetErrno()
168               << " Error sg:" << gnss_out_->StrError();
169           }
170         }
171       }
172     }
173 
174     cuttlefish::SharedFD gnss_in_;
175     cuttlefish::SharedFD gnss_out_;
176     std::thread read_thread_;
177     std::thread file_read_thread_;
178     std::string cached_nmea;
179     std::mutex cached_nmea_mutex;
180 };
181 
RunServer()182 void RunServer() {
183   auto gnss_in = cuttlefish::SharedFD::Dup(FLAGS_gnss_in_fd);
184   close(FLAGS_gnss_in_fd);
185   if (!gnss_in->IsOpen()) {
186     LOG(ERROR) << "Error dupping fd " << FLAGS_gnss_in_fd << ": "
187                << gnss_in->StrError();
188     return;
189   }
190   close(FLAGS_gnss_in_fd);
191 
192   auto gnss_out = cuttlefish::SharedFD::Dup(FLAGS_gnss_out_fd);
193   close(FLAGS_gnss_out_fd);
194   if (!gnss_out->IsOpen()) {
195     LOG(ERROR) << "Error dupping fd " << FLAGS_gnss_out_fd << ": "
196                << gnss_out->StrError();
197     return;
198   }
199   auto server_address("0.0.0.0:" + std::to_string(FLAGS_gnss_grpc_port));
200   GnssGrpcProxyServiceImpl service(gnss_in, gnss_out);
201   service.StartServer();
202   if (!FLAGS_gnss_file_path.empty()) {
203     service.StartReadFileThread();
204     // In the local mode, we are not start a grpc server, use a infinite loop instead
205     while(true) {
206       std::this_thread::sleep_for(std::chrono::milliseconds(2000));
207     }
208   } else {
209     ServerBuilder builder;
210     // Listen on the given address without any authentication mechanism.
211     builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
212     // Register "service" as the instance through which we'll communicate with
213     // clients. In this case it corresponds to an *synchronous* service.
214     builder.RegisterService(&service);
215     // Finally assemble the server.
216     std::unique_ptr<Server> server(builder.BuildAndStart());
217     std::cout << "Server listening on " << server_address << std::endl;
218 
219     // Wait for the server to shutdown. Note that some other thread must be
220     // responsible for shutting down the server for this call to ever return.
221     server->Wait();
222   }
223 
224 }
225 
226 
main(int argc,char ** argv)227 int main(int argc, char** argv) {
228   ::android::base::InitLogging(argv, android::base::StderrLogger);
229   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
230 
231   LOG(DEBUG) << "Starting gnss grpc proxy server...";
232   RunServer();
233 
234   return 0;
235 }
236