#!/usr/bin/env python

# check ethercat slaves configuration with SDO commands
# author: Sebastien BLANCHET
# version: 1.0
# date: 2014-07-24

# the program reads a configuration file and check value
# if a value mismatches, it proposes to overwrite it

# requirement:
#  - python 2.6
#  - IgH Ethercat Master  http://etherlab.org/en/ethercat/index.php

import os, shlex, sys, subprocess
from optparse import OptionParser

# full path to ethercat command
ethercatCmd="/opt/etherlab/bin/ethercat"
lineCounter=0


def getAbsoluteSlavePosition(alias, relpos):
    """ get slave absolution position
    parameters: alias   slave alias
                relpos  relative position
    """
    cmd = "%s -a %d -p %d slaves" % (ethercatCmd, alias, relpos )

    if options.verbose:
        print cmd

    args = shlex.split(cmd)
    process = subprocess.Popen( args, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE )

    (stdoutdata, stderrdata) = process.communicate()
#    print "STDOUT", stdoutdata
#    print "STDERR", stderrdata

    # check process return code
    if process.wait() != 0:
        print cmd
        print stderrdata
        print "Error while determining absolute slave position"
        sys.exit(1)

    elif len(stdoutdata) == 0:
        textMsg = "line %d, no slaves found @ %d:%d" % (lineCounter, alias, relpos)
        if options.skipMissing:
            print "Warning:", textMsg
            return
        else:
            print "Error:", textMsg
            sys.exit(1)

    # parse stdout
    tokens = stdoutdata.split()
    result = int(tokens[0])

    if options.verbose:
        print "alias %d:%d -> absPos %d" % (alias, relpos, result)

    return int(result)



def parseValue( valueStr, dataType):
    """" parse string according to its data type"""

    integerTypes = [ "bool", "int8", "int16", "int32", "int64",
                     "uint8", "uint16", "uint32", "uint64" ]
    floatTypes = ["float", "double"]
    stringTypes= ["string"]

    if dataType in integerTypes:
        result = int( valueStr, 0 )

    elif dataType in floatTypes:
        result = float(valueStr)

    elif dataType in stringTypes:
        result = valueStr

    else:
        print "Error: unsupported data type: %s" % dataType
        sys.exit(1)

    return result


def printSdoComment( absPos, index, subindex):
    """ search comment in sdo dictionnary """
    cmd = "%s -p %d slaves" % (ethercatCmd, absPos )
    os.system( cmd )

    cmd = "%s -p %d sdos" % (ethercatCmd, absPos )

    if options.verbose:
        print cmd

    args = shlex.split(cmd)
    process = subprocess.Popen( args, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE )

    # get dictionnary output in stdoutdata
    (stdoutdata, stderrdata) = process.communicate()
#    print "STDOUT", stdoutdata
#    print "STDERR", stderrdata

    # check process return code
    if process.wait() != 0:
        print cmd
        print stderrdata
        print "command error: %s" % (cmd)
        sys.exit(1)

    # search comment in dictionnary and print it
    for line in stdoutdata.splitlines():
        if line.strip().startswith( "0x%04x:%02x" % (index, subindex) ):
            print line.lstrip()




def parseLine( line ):
    """ parse configuration line
    line syntax: alias relpos  index    subindex type  action  value
    """
    MaxColumnsNum = 7
    line = line.strip()

#    print line
    tokens = line.split(None, MaxColumnsNum-1)
    if len(line) == 0 or line.startswith("#"):
        return

#    print tokens

    if (len(tokens) != MaxColumnsNum):
        print "Error: line %d token #%d is missing" \
            % (lineCounter, len(tokens)+1)
        sys.exit(1)

    tokIdx=0 # token index
    try:
        alias = int(tokens[tokIdx], 0)
        tokIdx += 1
        relpos = int(tokens[tokIdx], 0) ;
        tokIdx += 1
        index = int(tokens[tokIdx], 0)
        tokIdx += 1
        subindex = int(tokens[tokIdx], 0)
        tokIdx += 1
        dataType = tokens[tokIdx]
        tokIdx += 1
        action = tokens[tokIdx]
        tokIdx += 1
        reqValueStr = tokens[tokIdx]
        reqValue = parseValue( reqValueStr, dataType)
        tokIdx += 1

    except ValueError:
        print "Error line %d, token #%d (%s ) has invalid value" \
            % ( lineCounter, tokIdx, tokens[tokIdx] )
        sys.exit(1)

    if options.dryRun:
        return


    absPos = getAbsoluteSlavePosition( alias, relpos)
    if absPos == None and options.skipMissing:
        return

    actValue = readValue( absPos, index, subindex, dataType )

#    print "act=", actValue, "req=", reqValue

    # check if actual value matches configuration file requested value
    if reqValue != actValue :
        print "Error: line %d values mismatch for %d:0x%x:0x%x" \
            ". act=%s, req=%s" \
            % (lineCounter, absPos, index, subindex, actValue, reqValue )
        printSdoComment( absPos, index, subindex)

        if action == "write":
            if options.autoYes:
                yesReplace = True
            else:
                ans = raw_input( "Do you want to write the new value [y/N] ? ")
                yesReplace = ans.lower().startswith("y")

            if not yesReplace:
                print "Exit without writing new value"
                sys.exit(1)

            writeValue( absPos, index, subindex, dataType, reqValueStr )

            # check value after writing
            actValue = readValue( absPos, index, subindex, dataType )
            if reqValue != actValue:
                print "Error: writing has failed"
                sys.exit(1)

        else:
            print "Error: values do not match"
            sys.exit(1)



def readValue( absPos, index, subindex, dataType):
    """ read a value through SDO
    parameters: absPos  absolute position
                index   sdo index
                subindex sdo subindex
                dataType sdo data type
    """

    cmd = "%s -p %d upload -t %s 0x%x 0x%x" \
        % (ethercatCmd, absPos, dataType, index, subindex )

    if options.verbose:
        print cmd

    args = shlex.split(cmd)
    process = subprocess.Popen( args, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE )

    (stdoutdata, stderrdata) = process.communicate()
#    print "STDOUT", stdoutdata
#    print "STDERR", stderrdata

    # check process return code
    if process.wait() != 0:
        print cmd
        print stderrdata
        print "Error line %d" % (lineCounter)
        sys.exit(1)

    # parse stdout
    tokens = stdoutdata.split()
    result = parseValue( tokens[0], dataType )

    if options.verbose:
        print "read", result, "@ %d:0x%x:0x%x" % ( absPos, index, subindex)

    return result



def writeValue( absPos, index, subindex, dataType, reqValueStr):
    """ write a value through SDO
    parameters: absPos  absolute position
                index   sdo index
                subindex sdo subindex
                dataType sdo data type
                reqValueStr requested value as string
    """
    cmd = "%s -p %d download -t %s 0x%x 0x%x %s" \
        % (ethercatCmd, absPos, dataType, index, subindex, reqValueStr )

    if options.verbose:
        print cmd

    args = shlex.split(cmd)
    process = subprocess.Popen( args, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE )

    (stdoutdata, stderrdata) = process.communicate()
#    print "STDOUT", stdoutdata
#    print "STDERR", stderrdata

    # check process return code
    if process.wait() != 0:
        print cmd
        print stderrdata
        sys.exit(1)



def main():
    global ethercatCmd
    global lineCounter
    global options

    usage = "usage: %prog CONFIG_FILENAME"
    parser = OptionParser( usage,
                           description="Check ethercat slaves configuration" \
                               " through SDO commands")


    parser.add_option( "-d", "--dry-run",
                       dest="dryRun",
                       action="store_true",
                       default=False,
                       help="dry run: parse config file but emit no ethercat"\
                           " commands"
                       )
    parser.add_option( "-m", "--master",
                       dest="masterIndex",
                       type="int",
                       default=0,
                       help="ethercat master index: Default = %default"
                       )


    parser.add_option( "-s", "--skip-missing",
                       dest="skipMissing",
                       action="store_true",
                       default=False,
                       help="skip missing slaves"
                       )


    parser.add_option( "-v", "--verbose",
                       dest="verbose",
                       action="store_true",
                       default=False,
                       help="verbose mode"
                       )

    parser.add_option( "-y", "--yes",
                       dest="autoYes",
                       action="store_true",
                       default=False,
                       help="automatically answer yes to all questions"
                       )

    ( options, args ) = parser.parse_args()

    if options.masterIndex:
        ethercatCmd += " -m %d" % options.masterIndex

    if len(args) == 0:
        print "Error, config file is missing"
        sys.exit(1)

    configFile = args[0];
    file = open( configFile, 'r')
    for line in file:
        lineCounter = lineCounter + 1
        parseLine( line )

    sys.exit( 0 )



if __name__ == "__main__":
	main()
