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