/*-----------------------------------------------------------------------------
 * meter.c -- Linux CPU meter v0.2.  Uses circuit described in led-stat.txt
 * Usage: meter [-m 0-4] [-p io_addr] [-t usecs] [-l max_load] [-n leds] [-h]
 *
 * AUTHORS:
 *  Brian D. Chase (chasebd@nextwork.rose-hulman.edu) -- 23 Jan 1995,
 *  based on original version by Joseph W. Vigneau (joev@wpi.edu) (c)1994.
 * REVISIONS:
 * v0.1  23 Jan 1995 
 *  Removed gross spawning of ps to gather CPU load info. 
 *  Added command line options for lots of things.
 *  Added led_mask table for speed.
 *  Tried to optimize main polling loop reasonably well.
 *  Pretty much a rewrite :-)
 * v0.2  29 Jan 1995
 *  Added new modes: hacked traditional, cylon, inverted cylon, pulse,
 *     inverted pulse, random.
 *  Lost a little efficiency but gained a lot of functionality.
 *  Changed -u option to -t option.
 * NOTES:
 *  This program is covered by GNU's GPL.
 *---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include "port.h"

/* defines for default values */
#define LPT1_PORT 0x378              /* LPT1's usual I/O address */
#define LPT2_PORT 0x278              /* LPT2's usual I/O address */
#define MAX_LOAD  2.0                /* default load scaling factor */
#define MAX_LEDS  8                  /* maximum num of LEDs available */
#define U_SECS    1000000L           /* default to 1.0 sec intervals */
#define SPEED_UP  0
#define SLOW_DOWN 1
#define square(A) ((A)*(A))          /* quick and easy squaring macro */

/* usage message */
const char usage_mesg[] =
"Usage: %s [-m 0-4] [-p io_addr] [-t usecs] [-l max_load] [-n leds] [-h]\n \
\t-m [0-4]\tvarious meter modes\n \
\t   mode 0\ttraditional mode (default mode)\n \
\t   mode 1\tnormal cylon \\ Knight Rider mode\n \
\t   mode 2\tinverted cylon \\ Knight Rider mode\n \
\t   mode 3\tnormal pulse mode\n \
\t   mode 4\tinverted pulse mode\n \
\t   mode 5\trandom mode\n \
\t-p io_addr\tparallel port I/O address (default 0x378)\n \
\t\t\tLPT1 usually = 0x378, LPT2 usually = 0x278\n \
\t-t usecs\tnumber of usecs between updates (default 1000000)\n \
\t-l max_load\tupper limit load average (default 2.0)\n \
\t\t\t(if load_avg >= max_load then all LEDs lit)\n \
\t-n leds\t\tnumber of LEDs to use (default 8)\n \
\t-h\t\thelp (Display this info)\n\n";

/* function prototypes */
int run_traditional(float ld_avg, float ld_max, int leds_num, 
		    unsigned int port, long usecs);
int run_cylon(float ld_avg, float ld_max, int leds_num, unsigned int port,
	      long usecs, int mode_flag);
int run_pulse(float ld_avg, float ld_max, int leds_num, unsigned int port, 
	      long usecs, int mode_flag);
int run_random(float ld_avg, float ld_max, int leds_num, unsigned int port,
	       long usecs);
int run_random2(float ld_avg, float ld_max, int leds_num, unsigned int port,
		long usecs);

/*=== MAIN ===*/
int main(int argc, char **argv)
{
   FILE *fp;                         /* file ptr to load average info */
   int c;                            /* temp int for character storage */
   int run_mode = 0;                 /* meter mode to use */
   int leds = MAX_LEDS;              /* num of LEDs to use */
   long us_count = U_SECS;           /* num of usecs between pollings */
   unsigned int lp_port = LPT1_PORT; /* parallel port I/O address */
   float load_avg = 0.0;             /* load average value */
   float load_max = MAX_LOAD;        /* maximum spec load for scaling uses */

   /* option handling */
   while ((c=getopt(argc, argv, "m:p:t:l:n:h")) != EOF)
   {
      switch (c) {
      case 'm' :   /* meter mode option */
	 sscanf(optarg, "%d", &run_mode);
	 break;
      case 'p' :   /* lp port specifier */
	 sscanf(optarg, "%x", &lp_port);
	 break;
      case 't' :   /* check time for load avg */
	 sscanf(optarg, "%ld", &us_count);
	 break;
      case 'l' :   /* load to max out at */
	 sscanf(optarg, "%f", &load_max);
	 break;
      case 'n' :   /* number of leds to use */
	 if (((leds = atoi(optarg)) > MAX_LEDS) || (leds <= 0)) {
	    fprintf(stderr, "%s: bad number of LEDs specified.\n", argv[0]);
	    exit(EXIT_FAILURE);
	 }
	 break;
      case 'h' :   /* help option */ 
	 printf(usage_mesg, argv[0]);
	 exit(EXIT_SUCCESS);
	 break;
      case '?' :   /* invalid options case */ 
      default :
	 fprintf(stderr, usage_mesg, argv[0]);
	 exit(EXIT_FAILURE);
	 break;
      }
   }

   /* set access permissions on the port */
   if ((ioperm(lp_port, 1, 1)) == -1) {
      fprintf(stderr, "%s: port 0x%x invalid.\n", argv[0], lp_port);
      perror(argv[0]);
      exit(EXIT_FAILURE);
   }

   /* main loop to poll for load average info */
   while (1) {
      /* get the load average info from /proc/loadavg file */
      if ((fp = fopen("/proc/loadavg", "r")) == NULL) {
	 fprintf(stderr, "couldn't open /proc/loadavg\n");
	 exit(EXIT_FAILURE);
      }
      fscanf(fp, "%f", &load_avg);
      fclose(fp);

      switch (run_mode) {
      case 0 :  /* traditional mode */
	 run_traditional(load_avg, load_max, leds, lp_port, us_count);
	 break;
      case 1:   /* cylon mode */ 
	 run_cylon(load_avg, load_max, leds, lp_port, us_count, SPEED_UP);
	 break;
      case 2:   /* inverted cylon mode */
	 run_cylon(load_avg, load_max, leds, lp_port, us_count, SLOW_DOWN);
	 break;
      case 3:   /* pulse mode */
	 run_pulse(load_avg, load_max, leds, lp_port, us_count, SPEED_UP);
	 break;
      case 4:   /* inverted pulse mode */
	 run_pulse(load_avg, load_max, leds, lp_port, us_count, SLOW_DOWN);
	 break;
      case 5:   /* random mode */
	 run_random(load_avg, load_max, leds, lp_port, us_count);
	 break;
      default:  /* catch-all error case */ 
	 fprintf(stderr, "%s: invalid mode (%d) specified.\n", 
		 argv[0], run_mode);
	 exit(EXIT_FAILURE);
	 break;
      }
   }
   exit(EXIT_SUCCESS);
}
/*=== END MAIN ===*/

/*-----------------------------------------------------------------------------
 * run_traditional - orginal flavour LED meter.
 */
int run_traditional(float ld_avg, float ld_max, int leds_num, 
		    unsigned int port, long usecs)
{
   /* lookup table to use for bit mask */ 
   static unsigned char led_mask[] = 
   {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; 
   int num_lights;
   
   /* take load_avg and use it to specify num of lights to turn on */
   if (ld_avg/ld_max > 1.0)
      num_lights = MAX_LEDS;
   else
      num_lights = (int) (ld_avg/ld_max * leds_num);

   port_out(port, led_mask[num_lights]);  /* turn on the lights */
   usleep(usecs);                         /* sleep */

   return 0;
}

/*-----------------------------------------------------------------------------
 * run_cylon - Battlestar Galactica Cylon and Knight Rider Kit meter mode.
 *   the lights cycle from low to high and back, kind of like 1D pong.
 */
int run_cylon(float ld_avg, float ld_max, int leds_num, unsigned int port, 
	      long usecs, int mode_flag)
{
#define CY_IDLE_QUANTUM  500000L      /* longest quantum time slice */
#define CY_MIN_QUANTUM   10000        /* shortest quantum time slice */
#define CYD_IDLE_QUANTUM 10000        /* idle speed in inverted pulse */
   static unsigned char cylon_mask[] = /* bit mask table */
   {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
   static int led_index;              /* index into led mask table */
   static int direction;              /* keeps track of LED direction */ 
   float change_rate;                 /* scaling factor applied to load avgs */
   int led_quantum;                   /* time slice alotted to each led */ 
   int i, count;                      /* misc counters */

   /* setup cylon to increase the frequency of led cycling as */
   /* the load average increases */
   if (mode_flag == SPEED_UP) {
      change_rate = -((float) CY_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * ld_avg) + 
	 CY_IDLE_QUANTUM+CY_MIN_QUANTUM;
   }
   /* setup cylon to decrease the frequency of led cycling as */
   /* the load average decreases */
   else if (mode_flag == SLOW_DOWN) {
      change_rate = (float)(usecs - CYD_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * square(ld_avg)) + CYD_IDLE_QUANTUM;
   }
   else return -1;  /* error return (currently unused) */
  
   /* setup the cycles based on the current time quantum */
   /* ensuring that at least one cycle is run */
   count = (usecs/led_quantum > 0 ? usecs/led_quantum : 1);

   /* controlling loop for cylon cycling. (practicing safe port_out() usage) */
   for (i=0; i < count; i++) {
      if (direction == 0 && led_index < leds_num-1) {
	 port_out(port, cylon_mask[led_index]);
	 led_index++;
      }
      else if (direction == 0 && led_index == leds_num-1) {
	 port_out(port, cylon_mask[led_index]);
	 led_index--;
	 direction = 1;
      }
      else if (direction == 1 && led_index > 0) {
	 port_out(port, cylon_mask[led_index]);
	 led_index--;
      }
      else if (direction == 1 && led_index == 0) {
	 port_out(port, cylon_mask[led_index]);
	 led_index++;
	 direction = 0;
      }
      else fprintf(stderr, "Missed a case silly\n");
      
      usleep(led_quantum);
   }
   return 0;
}

/*-----------------------------------------------------------------------------
 * run_pulse - sequence through the LEDs from lowest to highest.  In normal
 *   pulse mode, cycling goes faster as load increases.  In inverted mode,
 *   cycling is fastest when the system is idle.
 */
int run_pulse(float ld_avg, float ld_max, int leds_num, unsigned int port, 
	      long usecs, int mode_flag)
{
#define PU_IDLE_QUANTUM  500000L      /* longest quantum time slice */
#define PU_MIN_QUANTUM   10000        /* shortest quantum time slice */
#define PUD_IDLE_QUANTUM 10000        /* idle speed in inverted pulse */
   static int led_index;              /* index of led to ligh up */ 
   float change_rate;                 /* scaling factor applied to load avgs */
   int led_quantum;                   /* time slice alotted to each led */ 
   int i, count;                      /* misc counters */

   /* setup pulse to increase the frequency of led cycling as */
   /* the load average increases */
   if (mode_flag == SPEED_UP) {
      change_rate = -((float) PU_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * ld_avg) + 
	 PU_IDLE_QUANTUM + PU_MIN_QUANTUM;
   }
   /* setup pulse to decrease the frequency of led cycling as */
   /* the load average decreases */
   else if (mode_flag == SLOW_DOWN) {
      change_rate = (float)(usecs - PUD_IDLE_QUANTUM)/ld_max;
      ld_avg = (ld_avg > ld_max ? ld_max : ld_avg);
      led_quantum = (int)(change_rate * square(ld_avg)) + PUD_IDLE_QUANTUM;
   }
   else return -1;  /* error return (currently unused) */

   /* setup the cycles based on the current time quantum */
   /* ensuring that at least one cycle is run */
   count = (usecs/led_quantum > 0 ? usecs/led_quantum : 1);

   /* loop through led lighting until:  i x led_quantum == usecs */ 
   for (i=0; i < count; i++) {
      if (led_index < leds_num) {
	 port_out(port, 1 << led_index);
	 led_index++;
      }
      else if (led_index >= leds_num) {
	 led_index = 0;
      }
      else fprintf(stderr, "This shouldn't happen\n");
      
      usleep(led_quantum);
   }
   return 0;
}

/*-----------------------------------------------------------------------------
 * run_random - output random bit patterns on the display with the number
 *   of leds lit up proportional to the load average.
 */
int run_random(float ld_avg, float ld_max, int leds_num, unsigned int port,
	       long usecs)
{
#define QUANTUM_DIV 4                  /* sort of a fudge factor */
   unsigned char mask;                 /* mask to output */  
   int num_bits;                       /* number of bits to set in mask */
   int i, j;                           /* misc counters */

   /* get the number of bits we'll randomly set */ 
   if (ld_avg/ld_max > 1.0)
      num_bits = MAX_LEDS;
   else
      num_bits = (int) (ld_avg/ld_max * leds_num);

   /* set a random collection of bits in the mask proportional to the */
   /* load average.  it's not entirely perfect in that if you're setting */
   /* num_bits in the mask, some of the same bits may get set more than */
   /* once causing less than num_bit unique bits.  this isn't a real */
   /* problem... it still looks okay. */   
   for (j=0; j < QUANTUM_DIV; j++) {
      mask = 0;
      for (i=0; i < num_bits; i++) {
	 mask |= 1 << (random() % leds_num);
      }
      port_out(port, mask);
      usleep(usecs/QUANTUM_DIV);
   }
   return 0;
}

/**** END OF FILE ****/
