/* Program to check versions of Fluke 2 and Scribbler firmware
   and to update, if desired

   This program requires Fluke 2 and Scribble 2 hardware.
   The Fluke [1] and Scribbler [1] platforms are NOT supported.   
*/
#include "MyroC.h"
#include "../MyroC-utilities.h"
#include <stdio.h> 
#include <stdlib.h> 
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>   //needed for stat query on file size
#include <curl/curl.h> //allows Internet access; compile with -lcurl
#include <sys/stat.h>  //used to find file size of local firmware file

/* compile with line

 */

/* flags to specify if Scribbler echoes a command
   (most or all do)
*/
#define ECHO_COMMAND_ON  1
#define ECHO_COMMAND_OFF 0

/* flags to specify if Scribbler echoes all sensor data */
#define ECHO_SENSOR_ON   1
#define ECHO_SENSOR_OFF  0
#define BYTES_OF_SENSOR_DATA 11

/* maximum size of strings for version numbers */
#define MAX_VERSION_STRING_SIZE 40

/* byte codes for messages identifying robot commands */
#define GET_SCRIB_PROGRAM   91  // with offset, returns the scribbler program buffer
#define SET_SCRIB_PROGRAM  122  // set scribbler program memory byte
#define SET_START_PROGRAM  123  // initiate scribbler programming process
#define SET_START_PROGRAM2 153  // initiate scribbler 2 programming process
#define SET_SCRIB2_RESET   154  // initiate scribbler 2 programming process
#define SET_SCRIB_BATCH    155  // initiate scribbler 2 programming process
#define UPDATE_FIRMWARE     40  // Updates the firmware of the robot 

/* URL directory for IPRE firmware */
//char * url_dir = "http://myro.roboteducation.org/upgrade/";
char * url_dir = "http://www.betterbots.com/upgrade/";

/* Ipre subdirectories */
char * scribbler_url_subdir = "scribbler2/";
char * fluke_url_subdir     = "fluke/";

/* URL filenames for scribbler firmware */
char * scribbler_filename = "scribbler2-upgrade-1.1.6.binary";
char * fluke_filename_hex = "fluke-upgrade-2.9.1.hex"; // hex version
char * fluke_filename_bin = "fluke-upgrade-2.9.1.bin"; // binary version from hex2bin
char * fluke2_filename    = "fluke2-upgrade-3.0.9.hex";

int socket_num;  // current socket used for Bluetooth communications

/* Index of function prototypes 
 */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * functions for firware checking and updating
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* identify firmware versions for both fluke and scribbler
 */
void getFirmwareVersions (char fluke_ver [],  char robot_ver [], 
                          char scribbler_ver [], char model_ver []);
int unpack_fluke_version (char fluke_ver[]);

/* download firmware from URL address to local file
 */
void downloadFirmware (char url_dir [], char url_subdir [], char filename []);

/* upload firmware from local files 
*/
void uploadFluke2Firmware (int initialize, char connection_name []); 
void uploadScribbler2Firmware (int initialize, char scribbler_filename [], 
                               char connection_name []); 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * main program
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

int main () 
{
  char fluke_ver [MAX_VERSION_STRING_SIZE];
  char robot_ver [MAX_VERSION_STRING_SIZE];
  char scribbler_ver [MAX_VERSION_STRING_SIZE];
  char model_ver [MAX_VERSION_STRING_SIZE];


  printf ("Program to identify and/or update Fluke 2 and Scribbler 2 firmware\n");
  printf ("   Warning:  This program requires Fluke 2 and Scribble 2 hardware\n");
  printf ("   The Fluke [1] and Scribbler [1] platforms are NOT supported\n");

  /* Section 0:  starting with designated Bluetooth connection ID */
  rSetOpeningExchange ('n');
  rSetBluetoothEcho ('n');

  /* designate the string for connecting to the robot:
     some examples follow */
  char connection_name [40] = "B4:D8:A9:00:0D:42"; //Fluke2-0D42
  //char connection_name [40] = "/dev/rfcomm0";
  //char connection_name [40] = "/dev/tty.Fluke2-0950-Fluke2";
  //char connection_name [40] = "/dev/tty.Fluke2-0958-Fluke2";
  //char connection_name [40] = "/dev/tty.Fluke2-0909-Fluke2";
  //char connection_name [40] = "/dev/tty.Fluke2-0913-Fluke2";

  printf ("program to initialize Fluke 2 and Scribbler 2 robot\n");
  printf ("default bluetooth connection string:  %s\n", connection_name);
  printf ("    type return to accept name or enter alternative string: ");
  char line [80];
  fgets (line, 80, stdin);
  int i = 0;
  int j = 0;

  /* skip over white space in input */
  while (i < strlen (line) && isspace (line[i]))
    i++;
  /* if non-white-space entered, use first token as connection string */
  if (i < strlen (line))
    {
        while (i < strlen (line) && !isspace (line[i]))
          {
            connection_name [j] = line [i];
            i++;
            j++;
          }
        connection_name [j] = 0;
    }

  int fluke_version = 0; // when querying robot: 0 - unknown; 1 - Fluke; 2 - Fluke2
  printf ("\n    enter 1 to search Fluke/Scribbler version data; 0 to skip:  ");
  int start_option;  // 1 - search Fluke/Scribbler version, 2 - skip
  scanf ("%d", &start_option);
  while ((start_option != 0) && (start_option != 1))
    {
      printf ("   enter 0 or 1:  ");
      scanf ("%d", &start_option);
    }
  /* clear input line */
  while (getchar() != '\n');

  if (start_option == 1)
    {
      printf ("    connecting to robot using string %s\n", connection_name);
      rConnect (connection_name);

      /* Section 1:  get version information */
      printf ("\nDETERMINING FLUKE/SCRIBBLER VERSION DATA\n");
      getFirmwareVersions (fluke_ver, robot_ver, scribbler_ver, model_ver);
      printf ("    Fluke firmware version:  %s\n", fluke_ver);
      printf ("    Scribbler firmware version:  %s\n", robot_ver);
      printf ("    Scribbler hardware version:  %s\n", scribbler_ver);
      printf ("    Scribbler model version:  %s\n", model_ver);

      /* determine fluke_version */
      fluke_version = unpack_fluke_version (fluke_ver);

      rDisconnect ();
      if (fluke_version == 1)
	{
	  printf ("installation of firmware requires Fluke 2 hardware:\n");
	  printf ("   Since a Fluke [1] is installed, further processing impossible\n");
	  return 0;
	}
    }


  printf ("    Fluke hardware version: %d\n", fluke_version);
  

  /* Section 2:  download firmware from URL to local machine */
  printf ("\nDOWNLOAD options\n");
  int continue_loop = 1;
  while (continue_loop)
    {
      printf ("    1 - DOWNLOAD Fluke 2 firmware\n");
      printf ("    2 - DOWNLOAD Scribbler 2 firmware\n");
      printf ("    3 - leave DOWNLOAD section\n");
      int down_option;
      printf ("    Enter option:  "); 
      scanf ("%d", &down_option);
      /* clear input line */
      while (getchar() != '\n');

      /* set up firmware url and file strings */
      switch (down_option)
        {
        case 1: /* fluke 2 firmware */
	  downloadFirmware (url_dir, fluke_url_subdir, fluke2_filename); 
          break;
        case 2: /* scribbler firmware */
          downloadFirmware (url_dir, scribbler_url_subdir, scribbler_filename); 
          break;
        case 3:
          continue_loop = 0;
          break;

        default: printf ("try again with option 1, 2, or 3:");
        }
    }

  /* Section 3:  upload firmware from local file to Scribbler */
  printf ("\nUPLOAD options\n");
  continue_loop = 1;
  while (continue_loop)
    {
      printf ("    1 - UPLOAD fluke firmware (Fluke 2 assumed)\n");
      printf ("    2 - UPLOAD Scribbler 2 firmware\n");
      printf ("    3 - leave upload section\n");
      int up_option;
      int sendMagicKey = 1;  
      printf ("    Enter option:  "); 
      scanf ("%d", &up_option);
      /* clear input line */
      while (getchar() != '\n');

      /* set up firmware url and file strings */
      switch (up_option)
        {
        case 1: /* Fluke 2 firmware */
	  uploadFluke2Firmware (start_option, connection_name); 
          break;
        case 2: /* scribbler firmware */
	  /* Fluke software after 2.9.1 and Fluke 2 software require a Magic Key */
	  sendMagicKey = 1;  
          uploadScribbler2Firmware (sendMagicKey, scribbler_filename, connection_name); 
          break;
        case 3:
          continue_loop = 0;
          break;

        default: printf ("try again with option 1, 2, or 3:");
        }
    }

  return 0;
}

/* Implementation of function prototypes
 */


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * functions for firmware checking and updating
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* identify firmware versions for both fluke and scribbler
 */
void getFirmwareVersions (char fluke_ver [],  char robot_ver [], 
                          char scribbler_ver [], char model_ver [])
{
  char message [9];
  message [0] = 80;  /* GET_INFO command */
  message [1] = 0;
  message [2] = 0;
  message [3] = 0;
  message [4] = 0;
  message [5] = 0;
  message [6] = 0;
  message [7] = 0;
  message [8] = 0;
  rSend (message, 9, ECHO_COMMAND_OFF, ECHO_SENSOR_OFF, "getFirmwareVersions");

  unsigned char response [200];
  int i = 0;

  rReceive (&response[i], 1);
  while (response[i] != '\n')
   {
     i++;
     rReceive (&response[i], 1);
    }
   response[i] = 0;

   /* unpack scribbler version information */
   char * token;

   //printf ("     %d, %s\n", i, response);
   /* fluke firmware version number comes first */
   token = strtok ((char *) response, ",");
   strncpy (fluke_ver, token, MAX_VERSION_STRING_SIZE);

   //printf ("     %s\n", fluke_ver);
   /* robot firmware version is second */
   token = strtok (NULL, ",");
   strncpy (robot_ver, token, MAX_VERSION_STRING_SIZE);

   //printf ("     %s\n", robot_ver);
  /* scribbler hardware version is third */
   token = strtok (NULL, ",");
   strncpy (scribbler_ver, token, MAX_VERSION_STRING_SIZE);


   //printf ("     %s\n", scribbler_ver);
   /* scribbler model is fourth */
   token = strtok (NULL, ",");
   strncpy (model_ver, token, MAX_VERSION_STRING_SIZE);

   //printf ("     %s\n", model_ver);
}

/* extract Fluke version from Fluke firmware version string */
int unpack_fluke_version (char fluke_ver[])
{
  /* make working copy of fluke version string */
  char temp_str [MAX_VERSION_STRING_SIZE];
  strncpy (temp_str, fluke_ver, MAX_VERSION_STRING_SIZE);

  /* 3-part version code comes after colon */
  char * token = strtok (temp_str, ":");  /* before colon */
  token = strtok (NULL, ".");             /* after colon, before . */

  if (strcmp (token, "2") == 0)
    return 1;                             /* Fluke (1) */
  if (strcmp (token, "3") == 0)
    return 2;                             /* Fluke 2 */
  return 0;                               /* Fluke version unknown */
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * functions to download firmware from URL address to local file
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
file-writing procedure used by the c url code
*/
static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
  int written = fwrite(ptr, size, nmemb, (FILE *)stream);
  return written;
}

/* download firmware from URL address to local file
 */
void downloadFirmware (char url_dir [], char url_subdir [], char filename [])
{
      char full_url [100];
      char ch;
      strcpy (full_url, url_dir);
      strcat (full_url, url_subdir);
      strcat (full_url, filename);
      printf ("     URL for firmware: %s\n", full_url);
      printf ("     local filename for firmware:  %s\n", filename);

      /* check if local file exists */
      FILE * local_file = fopen (filename, "r");
      if (local_file != NULL)
        {
          printf ("     local file %s exists:\n", filename);
          printf ("          overwrite existing file? (y or n):  ");
          char ch = tolower (getchar());
          while ((ch != 'y') && (ch != 'n'))
            ch = tolower (getchar()) ;
          /* clear input line */
          while (getchar() != '\n');

          if (ch == 'n')
            {  /* local file exists and should not be overwritten */
              printf ("firmware NOT downloaded from URL %s\n", full_url);
              return;
            }
          fclose (local_file);
        }

      /* download firmware from URL to filename */
      /* based on upgrade program by Nicholas Knoebber, fall, 2014 */
      local_file = fopen (filename, "w");
      printf ("     confirm download of firmware from URL to %s (y or n):  ", 
              filename);
      while ((ch = tolower (getchar()) != 'y') && (ch != 'n'));
      /* clear input line */
      while (getchar() != '\n');

      if (ch == 'n')
        {
          printf ("file NOT downloaded\n");
          return;
        }
      
      /* use CURL (or C URL) package in a standard C library 
         to download from a URL */
      CURL *curl_handle;

      curl_global_init(CURL_GLOBAL_ALL);
 
      /* init the curl session */ 
      curl_handle = curl_easy_init();
      /* set URL for firmware download 
      */
      curl_easy_setopt(curl_handle, CURLOPT_URL, full_url);
  
      /* no progress meter please */ 
      curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L);
 
      /* send all data to this function  */ 
      curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);
 
      local_file = fopen(filename,"w");
      if (local_file == NULL) {
        curl_easy_cleanup(curl_handle);
        return /*-1*/;
      }
      curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, local_file);
      curl_easy_perform(curl_handle);
      fclose(local_file);

      printf ("firmware downloaded from %s to %s\n", full_url, filename);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * upload firmware from local files 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* upload firmware to Fluke2 
 */
void uploadFluke2Firmware (int initialize, char connection_name [])
{
  /* confirm Fluke2 upload */
  printf ("          be sure the batteries are fresh --- this could take awhile\n");
  printf ("          confirm Fluke2 upgrade using %s (y or n):  ", 
          fluke2_filename);
  char ch = tolower (getchar());
  while ((ch != 'y') && (ch != 'n'))
    ch = tolower (getchar()) ;
  /* clear input line */
  while (getchar() != '\n');

  if (ch == 'n')
    {  /* STOP Fluke2 firmware upgrade */
      printf ("Fluke2 firmware NOT uploaded\n");
      return;
    }

  /* determine size of file */
  struct stat st; 
  int size = -1;
  if (stat(fluke2_filename, &st) == 0)
    size = st.st_size;

  if (size < 0)
    {
      printf ("    local Fluke2 firmware file %s not found\n", 
              fluke2_filename);
      printf ("    download the Fluke2 firmware to a local file and try again\n");
      return;
    }

  printf ("    opening connection to robot using string %s\n", connection_name);
  int socket_num = rConnect (connection_name);

  printf ("    uploading %d bytes from %s to the Fluke2\n", 
          size, fluke2_filename);

  /* send command to upload Fluke2 firmware */
  char upload_command [7];
  upload_command [0] = UPDATE_FIRMWARE;
  upload_command [1] = (unsigned char) 0x01;   // Magic code to ensure update
  upload_command [2] = (unsigned char) 0x23;   //     not done by accident
  upload_command [3] = (unsigned char) ((size >>24) & 0xFF); // high-order 8 bits
  upload_command [4] = (unsigned char) ((size >>16) & 0xFF); // high/middle-order 8 bits
  upload_command [5] = (unsigned char) ((size >> 8) & 0xFF); // middle/low-order 8 bits
  upload_command [6] = (unsigned char) (size & 0xFF);        // low-order 8 bits
  
  write (socket_num, upload_command, 7);

  /* upload firmware itself */
  int total_read = 0;
  /* send firmware in blocks, of transfer_size bytes */
  int transfer_size = 4096;
  int delay_per_transfer = 200000; // microsecond delay from one packet to next
  char block [transfer_size];

  /* upload firmware itself */
  int colPrint = 0;   // 0 - print progress in left column, 1 - right column
 FILE * firmware_handle = fopen (fluke2_filename, "r");
    while (total_read < size)
    {
      if (size - total_read < transfer_size)
        transfer_size = size - total_read;
      if (fread (block, transfer_size, 1, firmware_handle) < 1)
        {
          printf ("end of file %s encountered prematurely\n", 
                  scribbler_filename);
          return;
        }

      int total_written = 0;
      int amount_to_write = transfer_size;
      int amount_written;
      while (transfer_size > total_written)
        {
          amount_written = write (socket_num, block + amount_written, amount_to_write);
          //amount_written = write (1, block + amount_written, amount_to_write);
          total_written += amount_written;
          amount_to_write -= amount_written;
        }

      /* pause (according to delay_per_transfer) in micorseconds
         for Bluetooth communication of packet */
      usleep (delay_per_transfer);
      total_read += transfer_size;
      if (colPrint % 2 == 0)
        printf ("         progress: %7d bytes uploaded", total_read);
      else
        printf ("   %7d bytes uploaded    (out of %7d bytes)\n", total_read, size);
      fflush (stdout);
      colPrint++;
    }
  
    if (fread (block, 1, 1, firmware_handle) > 0)
      {
        printf ("Reading of file %s ended prematurely\n", 
                scribbler_filename);
        return;
      }
  fclose (firmware_handle);

  printf ("\n    Fluke2 firmware upload complete\n");
  printf ("\nInstalling update \n");
  printf ("     Do NOT turn off the Fluke 2 for a couple minutes\n");
  printf ("     RED activity light may come on after about 1,080,000 bytes\n");
  printf ("        and some beeps may be heard shortly thereafter\n");
  printf ("     However, this is only part way through the upload,\n");
  printf ("     Wait until all bytes are reported as being uploaded.\n");
  printf ("        Then wait awhile for the Fluke 2 to complete installation.\n");
 

}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* upload Scribbler 2 firmware
    *   assumes Myro firmware already installed on Fluke 2
    *   OK whether or not some version already installed
 */
void uploadScribbler2Firmware (int initialize, char scribbler_filename [], 
                               char connection_name [])
{
  /* determine size of file */
  struct stat st; 
  int size = -1;
  if (stat(scribbler_filename, &st) == 0)
    size = st.st_size;

  if (size < 0)
    {
      printf ("    local Scribbler2 firmware file %s not found\n", 
                    scribbler_filename);
      printf ("    download the Scribbler2 firmware to a local file and try again\n");
      return;
    }

  printf ("    opening connection to robot using string %s\n", connection_name);
  int socket_num = rConnect (connection_name);

  printf ("    uploading %d bytes from %s to the Scribbler2\n", 
               size, scribbler_filename);

  /* flush input and output */
  tcflush(socket_num, TCIOFLUSH);

  /* send command to upload Scribbler2 firmware */
  char upload_command [3];
  upload_command [0] = (unsigned char) SET_SCRIB_BATCH;
  upload_command [1] = (unsigned char) ((size >> 8)  & 0xFF);  // high-order 8 bits
  upload_command [2] = (unsigned char) (size & 0xFF);  // low-order 8 bits
  write (socket_num, upload_command, 3);

  /* upload firmware itself */
  int total_read = 0;
  /* send firmware in blocks, of transfer_size bytes */
  int transfer_size = 2048;
  char block [transfer_size];

  FILE * firmware_handle = fopen (scribbler_filename, "r");
    while (total_read < size)
    {
      if (size - total_read < transfer_size)
        transfer_size = size - total_read;
      if (fread (block, transfer_size, 1, firmware_handle) < 1)
        {
          printf ("end of file %s encountered prematurely\n", 
                  scribbler_filename);
          return;
        }
      write (socket_num, block, transfer_size);

      total_read += transfer_size;
      printf ("         progress:  %5d bytes uploaded\n", total_read);
    }
  
  if (fread (block, 1, 1, firmware_handle) > 0)
            {
          printf ("reading of file %s ended prematurely\n", 
                  scribbler_filename);
          return;
        }

  fclose (firmware_handle);


  if (initialize == 1)
    {
      /* send Magic Key 
         (command code, two specific hex characters and the file length)
         to confirm intention to update Scribbler2 firmware */
      printf ("    Recent Scribbler 2 firmware requires a Magic Key\n");
      printf ("         to help prevent accidental firmware changes\n");
      printf ("    Sending Magic Key\n");
      char magic_key [5];
      magic_key [0] = (unsigned char) SET_START_PROGRAM2;
      magic_key [1] = (unsigned char) 0x01;
      magic_key [2] = (unsigned char) 0x23; 
      magic_key [3] = (unsigned char) ((size >> 8)  & 0xFF);  // high-order 8 bits
      magic_key [4] = (unsigned char) (size & 0xFF);  // low-order 8 bits

      int a = (unsigned char) magic_key [3];
      int b = (unsigned char) magic_key [4];
      printf ("size reconstructed:  %d\n", (a*256) +b);
      write (socket_num, magic_key, 5);
    }

  sleep (15);

  printf ("    Scribbler2 firmware upload complete\n\n");
  printf ("After bright red light on Fluke appears, \n");
  printf ("KEEP SCRIBBLER 2 ROBOT TURNED ON UNTIL\n");
  printf ("     RED ACTIVITY LIGHT GOES OUT\n");
  printf ("  AND/OR ROBOT EMITS SEVERAL QUICK BEEPS\n\n");

  printf ("Note:  This program may or may not continue,\n");
  printf ("       depending upon the state of Bluetooth communications\n");
  printf ("       If program waits longer than a minute, terminate it manually\n");

  rDisconnect ();
} 



