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