/*
* 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 "../common/common_defs.h"
#include "../commhub/commhub.h"
#include "../commhub/client_utils.h"
int piu_fd = -1;
int hub_fd = -1;
device_test_vector piu_test_vector =
{
.dev_id = "rider_ui",
.init_string = "/0:\r/1:\r/m:09 00 03 42 5A 44 56\r",
.n_reply_lines = 4,
.reply_strings = (char *[]){NULL, NULL, "/OK:m:09 00 03 42 5A 44 56", "/M:^"},
};
char diag[DIAG_BUFFER_SIZE] = {0};
#define PIU_NUM_LINES 2
static char *piu_set_line_cmd[PIU_NUM_LINES] = {"/0:", "/1:"};
static piu_message def_msgs[PIU_NUM_LINES] = {{0}};
static piu_message cur_msgs[PIU_NUM_LINES] = {{0}};
int update_piu_display(int line)
{
piu_message *msg;
if( (line >= PIU_NUM_LINES) || (line < 0) )
{
return -1;
}
if(piu_fd < 0)
{
return -1;
}
msg = (cur_msgs[line].seconds == 0)?(def_msgs + line):(cur_msgs + line);
write(piu_fd, piu_set_line_cmd[line], 3); //write the command prefix
write(piu_fd, msg->message, strlen(msg->message)); //write the actual message
write(piu_fd, "\r", 1); //write the CR to signal EOL
// printf("%s%s\n",piu_set_line_cmd[line], msg->message);
return 0;
}
message_callback_return handle_display_message(struct message_record *msg, void *param)
{
piu_message *cmd = (piu_message *) msg->payload;
piu_message *tgt = NULL;
time_t now = time(NULL); //obtain a timestamp for NOW
int touched = 0;
//if we have an update for a line that doesn't exist, ignore it
if( (cmd->line >= PIU_NUM_LINES) || (cmd->line < 0) )
{
return MESSAGE_HANDLED_CONT;
}
if(cmd->seconds == 0) //if we're updating the default message
{
tgt = def_msgs + cmd->line; //get a pointer to our message target
//just do it...
memcpy(tgt, cmd, sizeof(piu_message));
//force terminate the string just in case
tgt->message[PIU_MESSAGE_LEN - 1] = '\0';
//priority is meaningless for default messages
tgt->priority = 0;
//if the active message on this line has expired, a change in
//default message will need to trigger an update
if(cur_msgs[cmd->line].seconds < now)
{
touched = 1;
}
}
else
{
tgt = cur_msgs + cmd->line; //get a pointer to our message target
//If the current message has expired or will expire this second (thus bringing up the default message)
//OR the new message is of higher priority then we need to do the repacement
if( (tgt->seconds <= now) || (cmd->priority >= tgt->priority) )
{
//perform the copy
memcpy(tgt, cmd, sizeof(piu_message));
//force terminate the string just in case
tgt->message[PIU_MESSAGE_LEN - 1] = '\0';
//set the expiration time on the new message to the timestamp of NOW
//plus the desired duration
tgt->seconds = now + cmd->seconds;
touched = 1;
}
}
if(touched) //if we have made any changes...
{
update_piu_display(cmd->line);
}
return MESSAGE_HANDLED_CONT;
}
int handle_message_timeouts()
{
int i;
time_t now = time(NULL);
//For each display line
for(i = 0; i < PIU_NUM_LINES; i++)
{
//Check to see if it has an active current message
if(cur_msgs[i].seconds != 0)
{
//If so, see if that message has passed its expiration time
if(cur_msgs[i].seconds < now)
{ //if it has...
cur_msgs[i].seconds = 0; //flag it inactive
update_piu_display(i); //and update the PIU
}
}
}
return 0;
}
#ifdef BEEP_WITH_MAGSTRIPE_READER
char dup_notify_str[MAX_PAYLOAD_LENGTH] = {0};
long long dup_notify_usec = 0;
message_callback_return handle_DIU_beep(struct message_record *msg, void *param)
{
int is_dup;
long long dup_usec_delta = 0;
if(strncmp(msg->payload, dup_notify_str, MAX_PAYLOAD_LENGTH))
{
strncpy(dup_notify_str, msg->payload, MAX_PAYLOAD_LENGTH - 1);
dup_notify_str[MAX_PAYLOAD_LENGTH - 1] = '\0';
is_dup = 0;
dup_notify_usec = 0;
}
else
{
is_dup = 1;
dup_usec_delta = get_usec_time() - dup_notify_usec;
dup_notify_usec = get_usec_time();
}
switch(msg->payload[0])
{
case LOGLEVEL_EVENT:
if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
{
PIU_ACK_BEEP(piu_fd);
}
break;
case LOGLEVEL_REJECT:
if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
{
PIU_ERROR_BEEP(piu_fd);
}
break;
case LOGLEVEL_ACCEPT:
if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
{
PIU_ACK_BEEP(piu_fd);
}
break;
case LOGLEVEL_ERROR:
if(!is_dup || (dup_usec_delta >= DUP_USEC_BEEP_THRESHOLD))
{
PIU_CRITICAL_BEEP(piu_fd);
}
break;
default:
break;
}
return MESSAGE_HANDLED_CONT;
}
#endif
//-------------------------
void maintain_ipc_hub_connect(char *progname)
{
struct message_record outgoing_msg;
if(hub_fd < 0) //if we have no connection to the IPC hub
{
hub_fd = connect_to_message_server(progname); //try and get one
if(hub_fd >= 0)
{
//Subscribe to the default status messages
subscribe_to_default_messages(hub_fd);
//Subscribe to our specific message
prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_PIU_MESSAGE, strlen(MAILBOX_PIU_MESSAGE));
send_message(hub_fd,&outgoing_msg);
#ifdef BEEP_WITH_MAGSTRIPE_READER
//if BEEP_WITH_MAGSTRIPE_READER is defined, that means our diu board's speaker/amp is FUCKING BROKEN and we
//need to intercept DIU messages and induce the magstripe reader on the PIU to beep when they occur even
//though it is a stupid idea to do so since any other activity on the magstripe reader (like swiping a card...)
//will cause it to either miss the card or disrupt the beeping.
prepare_message(&outgoing_msg, MAILBOX_SUBSCRIBE, MAILBOX_DRIVER_NOTIFY, strlen(MAILBOX_DRIVER_NOTIFY));
send_message(hub_fd,&outgoing_msg);
#endif
}
else
{
fprintf(stderr, "Cannot connect to IPC hub!\n");
}
}
}
int main(int argc, char **argv)
{
char line[LINE_BUFFER_SIZE] = {0};
struct message_record incoming_msg;
struct message_record outgoing_msg;
struct pollfd fds[2];
int nfd;
int poll_return;
int read_return;
int i, j;
configure_signal_handlers(argv[0]);
maintain_ipc_hub_connect(argv[0]);
//Register our defualt system message processing callbacks
register_system_status_callbacks();
//Register our module specific message processing callbacks
register_dispatch_callback(MAILBOX_PIU_MESSAGE, CALLBACK_USER(1), &handle_display_message, NULL);
#ifdef BEEP_WITH_MAGSTRIPE_READER
register_dispatch_callback(MAILBOX_DRIVER_NOTIFY, CALLBACK_USER(2), &handle_DIU_beep, NULL);
#endif
while( exit_request_status == EXIT_REQUEST_NONE ) //loop until we get asked to exit...
{
RESET_WATCHDOG();
//DEBUG
printf("[%lli] piu_minder: heartbeat\n", get_usec_time());
//DEBUG
maintain_ipc_hub_connect(argv[0]);
if(piu_fd < 0)
{
piu_fd = open_rs232_device(PASSENGER_UI_PORT, USE_DEFAULT_BAUD, RS232_LINE);
if(piu_fd >= 0)
{
read_return = test_and_init_device(piu_fd, &piu_test_vector, diag);
if(read_return)
{
fprintf(stderr, "PIU Init Failed on %s: %s\n", PASSENGER_UI_PORT, diag);
close(piu_fd);
piu_fd = -1;
}
else //If we successfully connected...
{
//iterate through each line
for(j = 0; j < PIU_NUM_LINES; j++)
{
update_piu_display(j); //and update the display to be current
}
}
}
else
{
fprintf(stderr, "Cannot open serial port %s for PIU!\n", PASSENGER_UI_PORT);
}
}
nfd = 0;
if(hub_fd >= 0)
{
fds[nfd].fd = hub_fd;
fds[nfd].events = POLLIN;
fds[nfd].revents = 0;
nfd++;
}
if(piu_fd >= 0)
{
fds[nfd].fd = piu_fd;
fds[nfd].events = POLLIN;
fds[nfd].revents = 0;
nfd++;
}
if(nfd > 0) //if we have any file descriptors, poll them
{
poll_return = poll(fds, nfd, POLL_TIMEOUT);
}
else //otherwise, whistle and look busy
{
poll_return = 0;
sleep(1);
}
//--------------------------------------------------------------------------------
//No matter what poll() returned, we need to do some basic background work here...
//--------------------------------------------------------------------------------
handle_message_timeouts();
//--------------------------------------------------------------------------------
if(poll_return < 1) //if poll didn't net us any work to do,
{
continue; //lets try again
}
for(i = 0; i < nfd; i++)
{
if( fds[i].fd == piu_fd ) //If we're looking at the PIU...
{
if(fds[i].revents & (POLLHUP | POLLERR | POLLNVAL)) //if poll says our serial port has become bogus...
{
fprintf(stderr, "This is very odd... Poll returned flags %d on our serial port...\n", fds[i].revents);
close(piu_fd); //close it
piu_fd = -1; //flag it invalid
break; //and break out of the for loop to allow the while to cycle
}
if(fds[i].revents & POLLIN)
{
read_return = read(fds[i].fd, line, sizeof(line));
if(read_return > 0)
{
char *trav = line;
line[read_return] = '\0';
strip_crlf(line);
while(*trav && (*trav != '/') ) //advance until EOL or we hit our start sentinel
{
trav++;
}
//Check to see that our address header is intact...
if( (trav[0] == '/') && (trav[2] == ':') )
{
switch(trav[1])
{
case 'M':
trav += 3; //advance past the header
//Ignore ACK message from the magstripe reader
if(strcmp(trav, "^"))
{
prepare_message(&outgoing_msg, MAILBOX_TOKEN_MAG, trav, strlen(trav) + 1);
send_message(hub_fd, &outgoing_msg);
}
break;
case 'R':
trav += 3; //advance past the header
//Ignore null reads.
if(strcmp(trav,"0|0"))
{
prepare_message(&outgoing_msg, MAILBOX_TOKEN_RFID, trav, strlen(trav) + 1);
send_message(hub_fd, &outgoing_msg);
}
break;
case '*': //handle warnings
case '#': //debugs
case '!': //and errors
format_log_message(&outgoing_msg, trav[1], "PIU Reports: %s", trav + 3); //send them all to the log server
send_message(hub_fd, &outgoing_msg);
if(trav[1] == '!') //but in the case of errors, send them to the driver too
{
format_driver_message(&outgoing_msg, trav[1], "PIU Reports: %s", trav + 3);
send_message(hub_fd, &outgoing_msg);
}
break;
default: //ignore any message addresses that we don't know what to do with
printf("Ignoring command \"%s\"\n", trav);
break;
}
}
else
{
// printf("Ignoring non-command line \"%s\"\n", trav);
}
}
else
{
fprintf(stderr, "Read from %s returned %d!\n", PASSENGER_UI_PORT, read_return);
close(piu_fd); //close it
piu_fd = -1; //flag it invalid
break; //and break out of the for loop to allow the while to cycle
}
}
}
else if( fds[i].fd == hub_fd ) //If we're looking at the IPC hub...
{
if(fds[i].revents & (POLLHUP | POLLERR | POLLNVAL)) //if poll says our connection to the IPC hub has died...
{
fprintf(stderr, "The connection to the IPC hub has gone away...\n"); //complain
close(hub_fd); //close it
hub_fd = -1; //flag it dead
break; //break out of the for loop
}
if(fds[i].revents & POLLIN) //if we have mail in any of our mailboxes...
{
read_return = get_message(hub_fd, &incoming_msg);
if(read_return < 0)
{
fprintf(stderr, "The connection to the IPC hub has gone away...\n"); //complain
close(hub_fd); //close it
hub_fd = -1; //flag it dead
break; //break out of the for loop
}
else
{
message_callback_return msg_status;
msg_status = process_message(&incoming_msg);
}
}
}
}
}
printf("Exiting.\n");
close(piu_fd);
close(hub_fd);
return 0;
}