/* * Copyright (c) 2019 Clementine Computing LLC. * * This file is part of PopuFare. * * PopuFare is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PopuFare is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with PopuFare. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common_defs.h" #include "../commhub/commhub.h" #include "../commhub/client_utils.h" #include "../common/gpsmath.h" typedef struct stop_struct //structure for loading stop definitions { int hour; //scheduled arrival time int min; double lat; //coordinates double lon; int route; //route int trip; //trip number int stop; //stop char name[STOP_NAME_LEN]; //human readable stop name } stop; int commhub_fd = -1; int current_paddle_num = 0; //Currently selected paddle int current_paddle_len = 0; //Number of stops on said paddle int current_paddle_idx = 0; //Index of the active stop on this paddle stop current_paddle[MAX_PADDLE_SIZE] = {{0}}; //Data block to hold loaded paddle int load_paddle(int paddlenum) { char buffer[LINE_BUFFER_SIZE]; char buffer2[LINE_BUFFER_SIZE]; FILE *f; int i, eol; int n; sprintf(buffer, "%s%d.paddle", CONFIG_FILE_PATH, paddlenum); f = fopen(buffer, "rb"); if(!f) { printf("Paddle not found: %s\n", buffer); return -1; } current_paddle_num = paddlenum; current_paddle_idx = 0; current_paddle_len = 0; n = 0; //For each line in the input file while( fgets(buffer, LINE_BUFFER_SIZE, f) ) { if(current_paddle_idx >= MAX_PADDLE_SIZE) { fprintf(stderr, "Paddle %d has overflowed its maximum size of %d stops and has been truncated!\n", current_paddle_num, MAX_PADDLE_SIZE); break; } strip_crlf(buffer); //get rid of any trailing CR/LF characters n++; i = eol = 0; i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); //Skip any blank or comment lines if(eol || buffer2[0] == '#') { continue; } //Clear this row memset(current_paddle + current_paddle_len, 0, sizeof(stop)); current_paddle[current_paddle_len].hour = strtol(buffer2, NULL, 10); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); if(eol) { printf("Line %d too short in paddle %d: \"%s\"\n", n, paddlenum, buffer); continue; } current_paddle[current_paddle_len].min = strtol(buffer2, NULL, 10); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); if(eol) { printf("Line %d too short in paddle %d: \"%s\"\n", n, paddlenum, buffer); continue; } current_paddle[current_paddle_len].lat = strtod(buffer2, NULL); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); if(eol) { printf("Line %d too short in paddle %d: \"%s\"\n", n, paddlenum, buffer); continue; } current_paddle[current_paddle_len].lon = strtod(buffer2, NULL); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); if(eol) { printf("Line %d too short in paddle %d: \"%s\"\n", n, paddlenum, buffer); continue; } current_paddle[current_paddle_len].route = strtol(buffer2, NULL, 10); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); if(eol) { printf("Line %d too short in paddle %d: \"%s\"\n", n, paddlenum, buffer); continue; } current_paddle[current_paddle_len].trip = strtol(buffer2, NULL, 10); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); if(eol) { printf("Line %d too short in paddle %d: \"%s\"\n", n, paddlenum, buffer); continue; } current_paddle[current_paddle_len].stop = strtol(buffer2, NULL, 10); i += get_field(buffer2, buffer + i, LINE_BUFFER_SIZE, &eol); strncpy(current_paddle[current_paddle_len].name, buffer2, STOP_NAME_LEN - 1); current_paddle_len++; } fclose(f); return paddlenum; } int clear_paddle() { current_paddle_num = 0; current_paddle_idx = 0; current_paddle_len = 0; return 0; } int where_the_hell_are_we() { int i; int found_stop = current_paddle_idx; //by default, we will report that we are still at our last known stop struct tm temp; time_t now; time_t sched; now = time(NULL); if(current_paddle_num == 0) { return -1; } #ifdef ROLLOVER_FORWARD_ONLY //If we are in rollover-forward mode, we will not actively re-select any previous stops, nor the current one. //This allows two stops to have the same location but different route numbers for places where the route changes at a stop. i = current_paddle_idx; #else i = 0; #endif for( /*see ifdef block above*/; i < current_paddle_len; i++) { localtime_r(&now,&temp); //populate our time structure based on now //so the date will be correct temp.tm_hour = current_paddle[i].hour; //Set the expected arrival time temp.tm_min = current_paddle[i].min; //Set the expected arrival time sched = mktime( &temp ); //and convert it back to a scheduled arrival time in UTC unix timestamp format if( (abs(now - sched) <= ROLLOVER_TIME_WINDOW) && //First we do the time check, because that's cheap integer math (GPS_Dist(gps_stat.lat, gps_stat.lon, current_paddle[i].lat, current_paddle[i].lon) <= ROLLOVER_DISTANCE) //The GPS distance last because that's expensive trig ) { found_stop = i; //update our found_stop index to the matching stop #ifndef ROLLOVER_TO_FURTHEST_STOP break; //if ROLLOVER_TO_FURTHEST_STOP is NOT defined, we break as soon as //we've found ANY matching stop (even the one we started at) #endif } } return found_stop; //Return found_stop } int send_status_update() { struct message_record outgoing_msg; stop_status current = {0}; if(commhub_fd < 0) { return -1; } current.paddle = current_paddle_num; if(current_paddle_num) { current.route = current_paddle[current_paddle_idx].route; current.trip = current_paddle[current_paddle_idx].trip; current.stop = current_paddle[current_paddle_idx].stop; current.lat = current_paddle[current_paddle_idx].lat; current.lon = current_paddle[current_paddle_idx].lon; strncpy(current.stopname, current_paddle[current_paddle_idx].name, STOP_NAME_LEN - 1); } prepare_message(&outgoing_msg, MAILBOX_STOP_STATUS, ¤t, sizeof(current)); return send_message(commhub_fd, &outgoing_msg); } //This function sends an update to the driver and to the diagnostic log saying we're doing a stop rollover int send_driver_update() { struct message_record outgoing_msg; if(commhub_fd < 0) { return -1; } if(current_paddle_num) { format_log_message(&outgoing_msg, LOGLEVEL_DEBUG, "%02d:%02d %s", current_paddle[current_paddle_idx].hour, current_paddle[current_paddle_idx].min, current_paddle[current_paddle_idx].name); send_message(commhub_fd, &outgoing_msg); format_driver_message(&outgoing_msg, LOGLEVEL_EVENT, "%02d:%02d %s", current_paddle[current_paddle_idx].hour, current_paddle[current_paddle_idx].min, current_paddle[current_paddle_idx].name); return send_message(commhub_fd, &outgoing_msg); } return -1; } int send_vault_drop() { struct message_record outgoing_msg; if(commhub_fd < 0) { return -1; } prepare_message(&outgoing_msg, MAILBOX_VAULT_DROP, "", 0); return send_message(commhub_fd, &outgoing_msg); } message_callback_return handle_status_req(struct message_record *msg, void *param) { send_status_update(); return MESSAGE_HANDLED_CONT; } message_callback_return handle_gps_update(struct message_record *msg, void *param) { int tempidx; //The system callback will have already handled this message and put it into the correct data structure //If either we have no current paddle, or no real GPS data, ignore this message if( (!current_paddle_num) || (!gps_stat.gps_good) ) { return MESSAGE_HANDLED_CONT; } tempidx = where_the_hell_are_we(); if(tempidx < 0) { return MESSAGE_HANDLED_CONT; } if(tempidx != current_paddle_idx) { current_paddle_idx = tempidx; send_status_update(); send_driver_update(); send_vault_drop(); } return MESSAGE_HANDLED_CONT; } message_callback_return handle_next_req(struct message_record *msg, void *param) { if(current_paddle_idx < (current_paddle_len - 1)) { current_paddle_idx++; send_driver_update(); } send_status_update(); return MESSAGE_HANDLED_CONT; } message_callback_return handle_prev_req(struct message_record *msg, void *param) { if(current_paddle_idx > 0) { current_paddle_idx--; send_driver_update(); } send_status_update(); return MESSAGE_HANDLED_CONT; } message_callback_return handle_set_paddle_req(struct message_record *msg, void *param) { struct message_record outgoing_msg; set_paddle_req *req = (set_paddle_req *)msg->payload; clear_paddle(); req->result = load_paddle(req->request); if(req->result > 0) { send_driver_update(); } prepare_message(&outgoing_msg, MAILBOX_PADDLE_ACK, req, sizeof(set_paddle_req)); send_message(commhub_fd, &outgoing_msg); send_status_update(); return MESSAGE_HANDLED_CONT; } void maintain_ipc_hub_connect(char *progname) { struct message_record outgoing_msg; if(commhub_fd < 0) //if we have no connection to the communication hub { commhub_fd = connect_to_message_server(progname); //try and get one // printf("commhub_fd = %d\n", commhub_fd); if(commhub_fd >= 0) //if it worked { //Subscribe to the command mailboxes we act on prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_SET_PADDLE, strlen(MAILBOX_SET_PADDLE)); send_message(commhub_fd,&outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_NEXT_STOP, strlen(MAILBOX_NEXT_STOP)); send_message(commhub_fd,&outgoing_msg); prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PREV_STOP, strlen(MAILBOX_PREV_STOP)); send_message(commhub_fd,&outgoing_msg); //Subscribe to the relevant status management mailboxes subscribe_to_default_messages(commhub_fd); //Request updated status information... prepare_message(&outgoing_msg, MAILBOX_STATUS_REQUEST, "", 0); send_message(commhub_fd,&outgoing_msg); } } } int main(int argc, char **argv) { struct pollfd fds[2]; int nfds = 0; int poll_return = 0; int read_return = 0; int i; struct message_record incoming_msg; configure_signal_handlers(argv[0]); maintain_ipc_hub_connect(argv[0]); //Register our default keep-up-with-system status callbacks register_system_status_callbacks(); //Add our module-specific callbacks register_dispatch_callback(MAILBOX_GPS_STATUS, CALLBACK_USER(1), handle_gps_update, NULL); register_dispatch_callback(MAILBOX_STATUS_REQUEST, CALLBACK_USER(2), handle_status_req, NULL); register_dispatch_callback(MAILBOX_SET_PADDLE, CALLBACK_USER(3), handle_set_paddle_req, NULL); register_dispatch_callback(MAILBOX_NEXT_STOP, CALLBACK_USER(4), handle_next_req, NULL); register_dispatch_callback(MAILBOX_PREV_STOP, CALLBACK_USER(5), handle_prev_req, NULL); while( exit_request_status == EXIT_REQUEST_NONE ) { RESET_WATCHDOG(); //DEBUG printf("[%lli] paddlemgr: heartbeat\n", get_usec_time()); //DEBUG maintain_ipc_hub_connect(argv[0]); nfds=0; if(commhub_fd >= 0) { fds[nfds].fd = commhub_fd; fds[nfds].events = POLLIN; nfds++; } if(nfds > 0) { poll_return = poll(fds, nfds, POLL_TIMEOUT); } else { usleep(POLL_TIMEOUT * 1000); poll_return = 0; } if(poll_return <= 0) { continue; } //---- If we got a message for(i=0; i < nfds; i++) { if( fds[i].fd == commhub_fd ) { //If we've lost connection, break this loop and poll all over again if(fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { close(commhub_fd); commhub_fd = -1; break; } if(fds[i].revents & POLLIN) { read_return = get_message(commhub_fd, &incoming_msg); if( read_return < 0 ) { close(commhub_fd); commhub_fd = -1; break; } process_message(&incoming_msg); //This passes the received message through the callback list } } } } if(commhub_fd >= 0) { close(commhub_fd); } printf("Goodbye.\n"); return 0; }