/*
* 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;
}