/*
* 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"
// structure for loading stop definitions
//
typedef struct stop_struct {
// scheduled arrival time
//
int hour;
int min;
// coordinates
//
double lat;
double lon;
int route;
int trip;
int stop;
// human readable stop name
//
char name[STOP_NAME_LEN];
} stop;
int commhub_fd = -1;
// Currently selected paddle
//
int current_paddle_num = 0;
// Number of stops on said paddle
//
int current_paddle_len = 0;
// Index of the active stop on this paddle
//
int current_paddle_idx = 0;
// Data block to hold loaded paddle
//
stop current_paddle[MAX_PADDLE_SIZE] = {{0}};
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;
// by default, we will report that we are still at our last known stop
//
int found_stop = current_paddle_idx;
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( ; i < current_paddle_len; i++) {
// populate our time structure based on now
// so the date will be correct
//
localtime_r(&now,&temp);
// Set the expected arrival time
//
temp.tm_hour = current_paddle[i].hour;
// Set the expected arrival time
//
temp.tm_min = current_paddle[i].min;
// and convert it back to a scheduled arrival time in UTC unix timestamp format
//
sched = mktime( &temp );
// First we do the time check, because that's cheap integer math
// then GPS distance last because that's expensive trig
//
if( (abs(now - sched) <= ROLLOVER_TIME_WINDOW) &&
(GPS_Dist(gps_stat.lat, gps_stat.lon, current_paddle[i].lat, current_paddle[i].lon) <= ROLLOVER_DISTANCE)) {
// update our found_stop index to the matching stop
//
found_stop = i;
#ifndef ROLLOVER_TO_FURTHEST_STOP
// 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)
//
break;
#endif
}
}
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 we have no connection to the communication hub
//
if(commhub_fd < 0) {
// try and get one
//
commhub_fd = connect_to_message_server(progname);
// if it worked
//
if(commhub_fd >= 0) {
// 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;
#ifdef DEBUG_PRINT
long long int _usec_now, _usec_prv, _usec_del;
_usec_now = get_usec_time();
_usec_prv = _usec_now;
_usec_del = 60000000;
#endif
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();
#ifdef DEBUG_PRINT
_usec_now = get_usec_time();
if ((_usec_now - _usec_prv) > _usec_del) {
printf("[%lli] paddlemgr: heartbeat\n", get_usec_time());
fflush(stdout);
_usec_prv = _usec_now;
}
#endif
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;
}
// This passes the received message through the callback list
//
process_message(&incoming_msg);
}
}
}
}
if(commhub_fd >= 0) {
close(commhub_fd);
}
printf("Goodbye.\n");
return 0;
}