pseudo 2 128 clue # clue.flac - Don't Ask
pseudo 3 3500 polo postorder # Polo Shirt! (With UCC Sun Logo)
pseudo 4 2500 membership # here comes the money!
-
+pseudo 5 1 testy mc test
-door 0 0 Open Door # Open Sesame
# Snack machine
// === FUNCTIONS ===
// --- Helpers --
+extern void AddPeriodicFunction(void (*Fcn)(void));
extern void CompileRegex(regex_t *Regex, const char *Pattern, int Flags);
extern int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage);
extern int InitSerial(const char *Path, int BaudRate);
#include <fcntl.h>
#include <regex.h>
#include <stdarg.h>
+#include <pthread.h>
#define READ_TIMEOUT 2 // 2 seconds for ReadChar
#define TRACE_COKE 1
// === PROTOTYPES ===
int Coke_InitHandler();
+ int Coke_int_GetSlotStatus(char *Buffer, int Slot);
+void Coke_int_UpdateSlotStatuses(void);
int Coke_CanDispense(int User, int Item);
int Coke_DoDispense(int User, int Item);
int Writef(const char *Format, ...);
char *gsCoke_SerialPort = "/dev/ttyS0";
int giCoke_SerialFD;
regex_t gCoke_StatusRegex;
+ int gaCoke_CachedStatus[7];
+pthread_mutex_t gCoke_Mutex = PTHREAD_MUTEX_INITIALIZER;
// == CODE ===
int Coke_InitHandler()
Writef("n5 Slot5\n");
WaitForColon();
Writef("n6 Coke\n");
+
+ Coke_int_UpdateSlotStatuses();
}
}
+ AddPeriodicFunction(Coke_int_UpdateSlotStatuses);
+
CompileRegex(&gCoke_StatusRegex, "^slot\\s+([0-9]+)\\s+([^:]+):([a-zA-Z]+)\\s*", REG_EXTENDED);
return 0;
}
+int Coke_int_GetSlotStatus(char *Buffer, int Slot)
+{
+ regmatch_t matches[4];
+ int ret;
+ char *status;
+
+ // Parse status response
+ ret = RunRegex(&gCoke_StatusRegex, Buffer, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response");
+ if( ret ) {
+ return -1;
+ }
+
+ // Get slot status
+ Buffer[ matches[3].rm_eo ] = '\0';
+ status = &Buffer[ matches[3].rm_so ];
+
+ #if TRACE_COKE
+ printf("Coke_CanDispense: Machine responded slot status '%s'\n", status);
+ #endif
+
+ if( strcmp(status, "full") == 0 ) {
+ gaCoke_CachedStatus[Slot] = 0; // 0: Avaliiable
+ return 0;
+ }
+ else {
+ gaCoke_CachedStatus[Slot] = 1; // 1: Empty
+ return 1;
+ }
+}
+
+void Coke_int_UpdateSlotStatuses(void)
+{
+ int i;
+ int len;
+ char tmp[40];
+
+ pthread_mutex_lock(&gCoke_Mutex);
+
+ if( WaitForColon() ) return ;
+ Writef("d7\r\n"); // Update slot statuses
+ WaitForColon();
+ Writef("s\n");
+ ReadLine(sizeof tmp, tmp); // Read back what we just said
+
+ for( i = 0; i <= 6; i ++ )
+ {
+ len = ReadLine(sizeof tmp, tmp);
+ if( len == -1 ) return ; // I give up :(
+ Coke_int_GetSlotStatus(tmp, i);
+ }
+ pthread_mutex_unlock(&gCoke_Mutex);
+}
+
int Coke_CanDispense(int UNUSED(User), int Item)
{
+ // Disabled in favor of caching
+ #if 0
char tmp[40], *status;
regmatch_t matches[4];
int ret;
while( tmp[0] == ':' || tmp[1] != 'l' )
{
ret = ReadLine(sizeof(tmp)-1, tmp);
+ if( ret == -1 ) return -1;
printf("ret = %i, tmp = '%s'\n", ret, tmp);
}
// Eat rest of response
WaitForColon();
- // Parse status response
- ret = RunRegex(&gCoke_StatusRegex, tmp, sizeof(matches)/sizeof(matches[0]), matches, "Bad Response");
- if( ret ) {
- return -1;
- }
-
- // Get slot status
- tmp[ matches[3].rm_eo ] = '\0';
- status = &tmp[ matches[3].rm_so ];
+ return Coke_GetSlotStatus(tmp, Item);
+ #else
+ // Sanity please
+ if( Item < 0 || Item > 6 ) return -1; // -EYOURBAD
- #if TRACE_COKE
- printf("Coke_CanDispense: Machine responded slot status '%s'\n", status);
+ // Can't dispense if the machine is not connected
+ if( giCoke_SerialFD == -1 )
+ return -2;
+
+ return gaCoke_CachedStatus[Item];
#endif
-
- if( strcmp(status, "full") == 0 )
- return 0;
-
- return 1;
}
/**
int Coke_DoDispense(int UNUSED(User), int Item)
{
char tmp[32];
- int ret;
+ int ret, len;
// Sanity please
if( Item < 0 || Item > 6 ) return -1;
if( giCoke_SerialFD == -1 )
return -2;
+ // LOCK
+ pthread_mutex_lock(&gCoke_Mutex);
+
#if TRACE_COKE
printf("Coke_DoDispense: flushing input\n");
#endif
// Read empty lines and echo-backs
do {
ret = ReadLine(sizeof(tmp)-1, tmp);
- if( ret == -1 ) return -1;
+ if( ret == -1 ) {
+ pthread_mutex_unlock(&gCoke_Mutex);
+ return -1;
+ }
#if TRACE_COKE
printf("Coke_DoDispense: read %i '%s'\n", ret, tmp);
#endif
// We think dispense worked
// - The machine returns 'ok' if an empty slot is dispensed, even if
// it doesn't actually try to dispense (no sound)
- return 0;
+ ret = 0;
}
-
- printf("Machine returned unknown value '%s'\n", tmp);
-
- return -1;
+ else {
+ printf("Machine returned unknown value '%s'\n", tmp);
+ ret = -1;
+ }
+
+ // Update status
+ Writef("s%i\r\n", Item);
+ len = ReadLine(sizeof tmp, tmp);
+ if(len == -1) gaCoke_CachedStatus[Item] = -1;
+ Coke_int_GetSlotStatus(tmp, Item);
+
+
+ pthread_mutex_unlock(&gCoke_Mutex);
+
+ return ret;
}
char ReadChar()
#include <ctype.h>
#include "common.h"
#include <regex.h>
-#include <sys/inotify.h>
-#include <signal.h>
-#include <sys/fcntl.h>
-#include <unistd.h>
+#include <sys/stat.h>
+#include <time.h>
// === IMPORTS ===
extern tHandler gCoke_Handler;
// === PROTOTYPES ===
void Init_Handlers(void);
-void ItemList_Changed(int signum);
void Load_Itemlist(void);
+void Items_ReadFromFile(void);
char *trim(char *__str);
// === GLOBALS ===
int giNumItems = 0;
tItem *gaItems = NULL;
+time_t gItems_LastUpdated;
tHandler gPseudo_Handler = {Name:"pseudo"};
tHandler *gaHandlers[] = {&gPseudo_Handler, &gCoke_Handler, &gSnack_Handler, &gDoor_Handler};
int giNumHandlers = sizeof(gaHandlers)/sizeof(gaHandlers[0]);
#if USE_INOTIFY
int giItem_INotifyFD;
#endif
+regex_t gItemFile_Regex;
// === CODE ===
void Init_Handlers()
#endif
/**
- * \brief Read the item list from disk
+ * \brief Read the initiali item list
*/
void Load_Itemlist(void)
{
- FILE *fp = fopen(gsItemListFile, "r");
+ int rv;
+ rv = regcomp(&gItemFile_Regex, "^-?([a-zA-Z][a-zA-Z]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED);
+ if( rv )
+ {
+ size_t len = regerror(rv, &gItemFile_Regex, NULL, 0);
+ char errorStr[len];
+ regerror(rv, &gItemFile_Regex, errorStr, len);
+ fprintf(stderr, "Rexex compilation failed - %s\n", errorStr);
+ exit(-1);
+ }
+
+ Items_ReadFromFile();
+
+ // Re-read the item file periodically
+ // TODO: Be less lazy here and check the timestamp
+ AddPeriodicFunction( Items_ReadFromFile );
+}
+/**
+ * \brief Read the item list from disk
+ */
+void Items_ReadFromFile(void)
+{
+ FILE *fp;
char buffer[BUFSIZ];
char *line;
int lineNum = 0;
- int i;
- regex_t regex;
+ int i, numItems = 0;
+ tItem *items = NULL;
regmatch_t matches[5];
-
- i = regcomp(®ex, "^-?([a-zA-Z][a-zA-Z]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED);
- if( i )
+
+ if( gItems_LastUpdated )
{
- size_t len = regerror(i, ®ex, NULL, 0);
- char *errorStr = malloc(len);
- regerror(i, ®ex, errorStr, len);
- fprintf(stderr, "Rexex compilation failed - %s\n", errorStr);
- free(errorStr);
- exit(-1);
+ struct stat buf;
+ if( stat(gsItemListFile, &buf) ) {
+ fprintf(stderr, "Unable to stat() item file '%s'\n", gsItemListFile);
+ return ;
+ }
+
+ // Check if the update is needed
+ if( gItems_LastUpdated > buf.st_mtime )
+ return ;
}
// Error check
+ fp = fopen(gsItemListFile, "r");
if(!fp) {
fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile);
perror("Unable to open item file");
if(strlen(line) == 0) continue;
// Pass regex over line
- if( (i = regexec(®ex, line, 5, matches, 0)) ) {
- size_t len = regerror(i, ®ex, NULL, 0);
- char *errorStr = malloc(len);
- regerror(i, ®ex, errorStr, len);
- fprintf(stderr, "Syntax error on line %i of item file '%s'\n%s", lineNum, gsItemListFile, errorStr);
- free(errorStr);
- exit(-1);
+ if( RunRegex( &gItemFile_Regex, line, 5, matches, NULL) ) {
+ fprintf(stderr, "Syntax error on line %i of item file '%s'\n", lineNum, gsItemListFile);
+ return ;
}
// Read line data
continue ;
}
- for( i = 0; i < giNumItems; i ++ )
+ for( i = 0; i < numItems; i ++ )
{
- if( gaItems[i].Handler != handler ) continue;
- if( gaItems[i].ID != num ) continue;
+ if( items[i].Handler != handler ) continue;
+ if( items[i].ID != num ) continue;
printf("Redefinition of %s:%i, updated\n", handler->Name, num);
- gaItems[i].Price = price;
- free(gaItems[i].Name);
- gaItems[i].Name = strdup(desc);
+ items[i].Price = price;
+ free(items[i].Name);
+ items[i].Name = strdup(desc);
break;
}
- if( i < giNumItems ) continue;
-
- gaItems = realloc( gaItems, (giNumItems + 1)*sizeof(gaItems[0]) );
- gaItems[giNumItems].Handler = handler;
- gaItems[giNumItems].ID = num;
- gaItems[giNumItems].Price = price;
- gaItems[giNumItems].Name = strdup(desc);
- gaItems[giNumItems].bHidden = (line[0] == '-');
- giNumItems ++;
- }
+ if( i < numItems ) continue;
+
+ items = realloc( items, (numItems + 1)*sizeof(items[0]) );
+ items[numItems].Handler = handler;
+ items[numItems].ID = num;
+ items[numItems].Price = price;
+ items[numItems].Name = strdup(desc);
+ items[numItems].bHidden = (line[0] == '-');
+ numItems ++;
+ }
+
+ // Clean up old
+ if( giNumItems )
+ {
+ giNumItems = 0;
+ free(gaItems);
+ gaItems = NULL;
+ }
+
+ // Replace with new
+ giNumItems = numItems;
+ gaItems = items;
+
+ gItems_LastUpdated = time(NULL);
}
char *trim(char *__str)
#include <fcntl.h>
#include <stdarg.h>
#include <syslog.h>
+#include <pthread.h>
#include "../cokebank.h"
// === IMPORTS ===
extern char *gsSnack_SerialPort;
extern char *gsDoor_Password;
+// === PROTOTYPES ===
+void *Periodic_Thread(void *Unused);
+
// === GLOBALS ===
int giDebugLevel = 0;
char *gsCokebankPath = "cokebank.db";
+// - Functions called every 20s (or so)
+#define ciMaxPeriodics 10
+struct sPeriodicCall {
+ void (*Function)(void);
+} gaPeriodicCalls[ciMaxPeriodics];
// === CODE ===
void sigint_handler()
int main(int argc, char *argv[])
{
int i;
+ pthread_t timer_thread;
// Parse Arguments
for( i = 1; i < argc; i++ )
Load_Itemlist();
+ pthread_create( &timer_thread, NULL, Periodic_Thread, NULL );
+
Server_Start();
+ pthread_kill(timer_thread, SIGKILL);
return 0;
}
+void *Periodic_Thread(void *Unused)
+{
+ int i;
+ Unused = NULL; // quiet, gcc
+
+ for( ;; )
+ {
+ sleep(10); // Sleep for a while
+ printf("Periodic firing\n");
+ for( i = 0; i < ciMaxPeriodics; i ++ )
+ {
+ if( gaPeriodicCalls[i].Function )
+ gaPeriodicCalls[i].Function();
+ }
+ }
+ return NULL;
+}
+
+void AddPeriodicFunction(void (*Fcn)(void))
+{
+ int i;
+ for( i = 0; i < ciMaxPeriodics; i ++ )
+ {
+ if( gaPeriodicCalls[i].Function ) continue;
+ gaPeriodicCalls[i].Function = Fcn;
+ return ;
+ }
+
+ fprintf(stderr, "Error: No space for %p in periodic list\n", Fcn);
+}
+
int RunRegex(regex_t *regex, const char *string, int nMatches, regmatch_t *matches, const char *errorMessage)
{
int ret;
if( Item->Handler->CanDispense )
{
- switch(Item->Handler->CanDispense(Item->ID, Client->UID))
+ switch(Item->Handler->CanDispense(Client->UID, Item->ID))
{
case 0: status = "avail"; break;
case 1: status = "sold"; break;