/*
* 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"
#define PIU_MINDER_VERSION "1.0.2"
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 the command prefix
//
write(piu_fd, piu_set_line_cmd[line], 3);
// write the actual message
//
write(piu_fd, msg->message, strlen(msg->message));
// write the CR to signal EOL
//
write(piu_fd, "\r", 1);
//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;
// obtain a timestamp for NOW
//
time_t now = time(NULL);
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 we're updating the default message
//
if(cmd->seconds == 0) {
// get a pointer to our message target
//
tgt = def_msgs + cmd->line;
// 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
{
// get a pointer to our message target
//
tgt = cur_msgs + cmd->line;
// 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 we have made any changes...
//
if(touched) {
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
// and update the PIU
//
update_piu_display(i);
}
}
}
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 we have no connection to the IPC hub
if(hub_fd < 0) {
// try and get one
//
hub_fd = connect_to_message_server(progname);
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;
#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
long long int _usec_ratelimit_now, _usec_ratelimit_prv, _usec_ratelimit_del;
_usec_ratelimit_now = get_usec_time();
_usec_ratelimit_prv = _usec_ratelimit_now;
_usec_ratelimit_del = 1000000;
if ( (argc>1) && (
(strncmp(argv[1], "-h", 3)==0) ||
(strncmp(argv[1], "-v", 3)==0) ) ) {
printf("piu_minder version: %s\n", PIU_MINDER_VERSION);
exit(0);
}
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
// loop until we get asked to exit...
//
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] piu_minder: heartbeat\n", get_usec_time());
_usec_prv = _usec_now;
}
#endif
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;
}
// If we successfully connected...
//
else {
// iterate through each line
//
for(j = 0; j < PIU_NUM_LINES; j++) {
// and update the display to be current
//
update_piu_display(j);
}
}
}
else {
_usec_ratelimit_now = get_usec_time();
if ((_usec_ratelimit_now - _usec_ratelimit_prv) > _usec_ratelimit_del) {
fprintf(stderr, "Cannot open serial port %s for PIU!\n", PASSENGER_UI_PORT);
_usec_ratelimit_prv = _usec_ratelimit_now;
}
}
}
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 we have any file descriptors, poll them
//
if(nfd > 0) {
poll_return = poll(fds, nfd, POLL_TIMEOUT);
}
// otherwise, whistle and look busy
//
else {
poll_return = 0;
sleep(1);
}
//--------------------------------------------------------------------------------
// No matter what poll() returned, we need to do some basic background work here...
//
//--------------------------------------------------------------------------------
handle_message_timeouts();
//--------------------------------------------------------------------------------
// if poll didn't net us any work to do,
//
if(poll_return < 1) {
//lets try again
//
continue;
}
for(i = 0; i < nfd; i++) {
//If we're looking at the PIU...
//
if( fds[i].fd == piu_fd ) {
// if poll says our serial port has become bogus...
//
if(fds[i].revents & (POLLHUP | POLLERR | POLLNVAL)) {
fprintf(stderr, "This is very odd... Poll returned flags %d on our serial port...\n", fds[i].revents);
// close it
//
close(piu_fd);
// flag it invalid
piu_fd = -1;
// and break out of the for loop to allow the while to cycle
//
break;
}
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);
// advance until EOL or we hit our start sentinel
//
while(*trav && (*trav != '/') ) {
trav++;
}
// Check to see that our address header is intact...
//
if( (trav[0] == '/') && (trav[2] == ':') ) {
switch(trav[1]) {
case 'M':
// advance past the header
//
trav += 3;
// 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':
// advance past the header
//
trav += 3;
// Ignore null reads and
// spurious 1-bit reads
//
if ( (strcmp(trav,"0|0")!=0) &&
(strcmp(trav,"1|0")!=0) &&
(strcmp(trav,"1|1")!=0) ) {
prepare_message(&outgoing_msg, MAILBOX_TOKEN_RFID, trav, strlen(trav) + 1);
send_message(hub_fd, &outgoing_msg);
}
break;
case 'Q':
trav += 3;
// Ignore ACK message
//
if(strcmp(trav, "^")) {
prepare_message(&outgoing_msg, MAILBOX_TOKEN_QR, trav, strlen(trav) + 1);
send_message(hub_fd, &outgoing_msg);
}
break;
// handle warnings
//
case '*':
// debugs
//
case '#':
// and errors
//
case '!':
// send them all to the log server
//
format_log_message(&outgoing_msg, trav[1], "PIU Reports: %s", trav + 3);
send_message(hub_fd, &outgoing_msg);
// but in the case of errors, send them to the driver too
//
if(trav[1] == '!') {
format_driver_message(&outgoing_msg, trav[1], "PIU Reports: %s", trav + 3);
send_message(hub_fd, &outgoing_msg);
}
break;
// ignore any message addresses that we don't know what to do with
//
default:
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 it
close(piu_fd);
//flag it invalid
piu_fd = -1;
//and break out of the for loop to allow the while to cycle
break;
}
}
}
//If we're looking at the IPC hub...
else if( fds[i].fd == hub_fd )
{
//if poll says our connection to the IPC hub has died...
if(fds[i].revents & (POLLHUP | POLLERR | POLLNVAL))
{
//complain
fprintf(stderr, "The connection to the IPC hub has gone away...\n");
//close it
close(hub_fd);
//flag it dead
hub_fd = -1;
//break out of the for loop
break;
}
// if we have mail in any of our mailboxes...
//
if(fds[i].revents & POLLIN) {
read_return = get_message(hub_fd, &incoming_msg);
if(read_return < 0) {
// complain
//
fprintf(stderr, "The connection to the IPC hub has gone away...\n");
// close it
//
close(hub_fd);
// flag it dead
//
hub_fd = -1;
// break out of the for loop
//
break;
}
else {
message_callback_return msg_status;
msg_status = process_message(&incoming_msg);
if (msg_status < 0) { }
}
}
}
}
}
printf("Exiting.\n");
close(piu_fd);
close(hub_fd);
return 0;
}