//
// Written July 2014 by John Lydic for Technologic Systems
// Used as example code for the TS-Modbus Devices connected to a TS-7670
//
// Requires libmodbus 3.0.2
// Compile commandline:
// gcc mbpeekpoke7670.c -o mbpeekpoke7670 -lmodbus -I./libmodbus-3.0.5/src -g
// 
// Substituting the appropriate version of the libmosbus library

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <asm/termios.h>
#include <modbus.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

typedef enum { false, true } bool;

#define CMD_BUF_SIZE 64
unsigned char cmdBuf[CMD_BUF_SIZE];

#define MAXTOREAD 32
#define CONNECT_STR_LENGTH 64

#define BPS300         300
#define BPS600         600
#define BPS1200       1200
#define BPS2400       2400
#define BPS4800       4800
#define BPS9600       9600
#define BPS19200     19200
#define BPS38400     38400
#define BPS57600     57600
#define BPS115200   115200
#define BPS230400   230400
#define BPS460800   460800
#define BPS1040000 1040000
#define BPS2080000 2080000
#define BPS4160000 4160000

uint32_t BitRates[] = {
    BPS300,
    BPS600,
    BPS1200,
    BPS2400,
    BPS4800,
    BPS9600,
    BPS19200,
    BPS38400,
    BPS57600,
    BPS115200,
    BPS230400,
    BPS460800,
    BPS1040000,
    BPS2080000,
    BPS4160000,
    0
};

bool CheckValidBitrate(uint32_t value)
{
    uint32_t *TablePtr = BitRates;

    if (value != 0)
    {
        while (*TablePtr++)
            if (*TablePtr == value)
                return true;
    }
    return false;
}

void Usage()
{
    fprintf(stderr, "\nUsage: mbpeekpoke7670 -switch parameter [OPTIOSN] ...\n"
            "Download and start a ZPU program.  Optionally persist the code\n"
            "Default parameter values in [brackets]\n"
            "  -?                     This help screen\n"
            "  -b  bit_rate           Bit rate [9600]\n" 
            "                         300, 600, 1200, 2400, 4800, 9600, 19200\n"
            "                         38400, 57600, 115200, 1000000, 4000000\n"
            "  -h                     This help screen\n"
            "  -n  number_of_items    number of items to read [1]\n"
            "  -r  address            Read: Register @ specified hex address\n"
            "  -s  slave_address      hexadecimal 00-f8; 00 is broadcast [0xf7]\n"
            "  -u                     Use USB connection\n"
            "  -v  data               value to write\n"
            "  -w  address            Write: Register @ specified address\n"
            " The (-w) write option requires a single (-v) variable\n"
            " The (-r) read option can read a number of items with the (-n) switch\n"
            );
    exit(0);
}

bool rOption = false;   // Reading
bool uOption = false;   // USB port
bool vOption = false;   // Data Value to write has been entered
bool wOption = false;   // Writing

extern char *optarg;
const char *optionsList = "?b:hn:r:s:uv:w:";

int main(int argc, char **argv)
{
    int aChar;
    uint16_t index;
    uint16_t writeData;
    uint16_t readBuffer[MAXTOREAD];
    uint16_t writeAddress;
    uint16_t readAddress;
    uint16_t numberToRead = 1;
    uint32_t bitRate = 9600;
    int slaveAddress = 0xf7;
    int rtnStatus = 0;
    uint8_t mbConnectionStr[CONNECT_STR_LENGTH+1];
    modbus_t *mbConnection;
    struct termios2 tio2;


    if (argc == 1)
        Usage();

    //
    //   Parse the arguments
    //
    while ((aChar = getopt(argc, argv, optionsList)) != -1)
    {
        switch (aChar)
        {
            case 'b':  // set bit rate
                bitRate = atoi(optarg);
                if (!CheckValidBitrate(bitRate))
                {
                    fprintf(stderr,"Invalid bit rate: %d\n", bitRate);
                    return 0;
                }
                break;

            case 'n':  // Number to read
                numberToRead = strtol(optarg, NULL, 16);
                if (numberToRead > MAXTOREAD)
                    numberToRead = MAXTOREAD;
                break;

            case 'r':  // Read
                readAddress = strtol(optarg, NULL, 16);
                rOption = true;
                break;

            case 's':  // Set Slave Address
                slaveAddress = strtol(optarg, NULL, 16);
                if ((slaveAddress == 0) || (slaveAddress > 0xf7))
                {
                    fprintf(stderr,"Slave ID (%0x02x) must be between 0x01 and 0xf7\n", slaveAddress);
                    return 0;
                }
                break;

            case 'u':  // USB port
                uOption = true;
                break;

            case 'v':  // Value to write
                writeData = strtol(optarg, NULL, 16);
                vOption = true;
                break;

            case 'w':  // write
                writeAddress = strtol(optarg, NULL, 16);
                if (writeAddress == 0 || writeAddress > 0x7e)
                {
                    fprintf(stderr,"Write Address (%0x02x) must be between 0x01 and 0xf7\n", writeAddress);
                    return 0;
                }
                wOption = true;
                break;

            case 'h':  //  Display help information
            case '?':  //  Display help information
            default:
                Usage();
        }
    }


    //  Validate entered parameters
    if ((readAddress + numberToRead) > 0x7e)
    {
        fprintf(stderr, "Read Address (0x%02x) PLUS Count (%d) must be <= 0x7e was: 0x%02x\n", readAddress, numberToRead, (readAddress + numberToRead));
        return 0;
    }

    if (uOption)
        strcpy(mbConnectionStr, "/dev/ttyACM0"); // USB direct
    else
        strcpy(mbConnectionStr, "/dev/ttySP2");  // RS-485

    //  Linux only directly handle up to 115200.  Bit rates above
    //  that get limited here and adjusted in the hwardware later.
    if (bitRate > BPS115200)  // libmodbus throws an error above 115200
        bitRate = BPS115200;

    // Open the modbus path
    if ((mbConnection = modbus_new_rtu(mbConnectionStr, bitRate, 'N', 8, 1)) == NULL)
    {
        fprintf(stderr,"Modbus port %s failed to open. Errno: %d, %s\n",
                mbConnectionStr, errno, modbus_strerror(errno));
        return 0;
    }

    // Set modbus post-setup options.
    modbus_set_slave(mbConnection, slaveAddress);
    if (rtnStatus  = modbus_connect(mbConnection))
    {
        fprintf(stderr,"File %s failed to open.\n", optarg);
        fprintf(stderr,"modbus_connect failed Errno: %d, %s\n", errno, modbus_strerror(errno));
        return 0;
    }

    //  Setup Linux serual port bit rates > 115200
    if (!uOption && bitRate > BPS115200)
    {
        int mbPortFd;

        mbPortFd = modbus_get_socket(mbConnection); // Get the port fd
        tcflush(mbPortFd, TCIFLUSH);
        ioctl(mbPortFd, TCGETS2, &tio2);
        tio2.c_cflag &= ~CBAUD;
        tio2.c_cflag |= BOTHER;
        tio2.c_ospeed = tio2.c_ispeed = bitRate;
        ioctl(mbPortFd, TCSETS2, &tio2);
    }

    if (!uOption)  // If using serial port (not USB), set RS-485 enable rate timer
    {
        sprintf(cmdBuf, "tshwctl --485speed=%d", bitRate);
        system(cmdBuf);
    }

    if (!wOption && !rOption)
    {
        fprintf(stderr, "Must specifiy Read (-r) or Write (-w)\n");
        exit(0);
    }

    // Must have selected either READ or WRITE
    if (wOption && !vOption)
    {
        fprintf(stderr, "WRITE requires a value (-v)\n");
        exit(0);
    }

    if (rOption) //  Perform read operation
    {
        //  Clear the buffer
        for (index = 0; index < MAXTOREAD; index++)
            readBuffer[index] = 0;

        //  Perform read operation
        rtnStatus = modbus_read_registers(mbConnection, (int) readAddress, (int) numberToRead, &readBuffer[0]);
        if (numberToRead == 1)
            printf("Read register 0x%02x: %04x\n", readAddress, readBuffer[0]);
        else
        {
            printf("Read %d registers @ 0x%02x: ", numberToRead, readAddress);
            for (index = 0; index < numberToRead; index++)
                printf("0x%04x ", readBuffer[index]);
            printf("\n");
        }
    }
    else  // Must be write
    {
        rtnStatus = modbus_write_register(mbConnection, writeAddress, writeData);
        if (rtnStatus < 0)
            printf("rtnStatus: %d failed writing %04X to holding register %04X  Error: %d 0 %s\n", rtnStatus, writeData, writeAddress, errno, modbus_strerror(errno));
        else
            printf("Value %04X written to HR %04X\n", writeData, writeAddress);
    }

    modbus_close(mbConnection);
    modbus_free(mbConnection);
    return 0;
}

