Exemplo n.º 1
0
class MyModem(MySupport):
    def __init__(self,
            port = "/dev/ttyAMA0" ,
            rate = 115200,
            loglocation = ProgramDefaults.LogPath,
            log = None,
            localinit = False,
            ConfigFilePath = ProgramDefaults.ConfPath,
            recipient = None):
        super(MyModem, self).__init__()

        self.MessagesSent = 0
        self.Errors = 0
        self.SendActive = False
        self.ModemLock = threading.RLock()
        self.Sending = False
        self.SendQueue = []

        if ConfigFilePath == None:
            self.ConfigFilePath = ProgramDefaults.ConfPath
        else:
            self.ConfigFilePath = ConfigFilePath

        # log errors in this module to a file
        if localinit == True:
            self.configfile = "mymodem.conf"
        else:
            self.configfile = os.path.join(self.ConfigFilePath, "mymodem.conf")

        # log errors in this module to a file
        if log == None:
            self.log = SetupLogger("mymodem", os.path.join(loglocation, "mymodem.log"))
        else:
            self.log = log

        self.console = SetupLogger("mymodem_console", log_file = "", stream = True)

        try:
            self.config = MyConfig(filename = self.configfile, section = "MyModem", log = self.log)

            self.LogAtCommands = self.config.ReadValue('log_at_commands', return_type = bool, default = False)

            self.MessageLevel = self.config.ReadValue('message_level', default = 'error')

            self.Rate = self.config.ReadValue('rate', return_type = int, default = 115200)

            self.Port = self.config.ReadValue('port', default = "/dev/ttyAMA0")

            self.Recipient = self.config.ReadValue('recipient', default = recipient)

            self.ModemType = self.config.ReadValue('modem_type', default = "LTEPiHat")

        except Exception as e1:
            self.LogErrorLine("Error reading config file: " + str(e1))
            self.LogConsole("Error reading config file: " + str(e1))
            return

        if self.Recipient == None or not len(self.Recipient):
            self.LogErrorLine("Error invalid recipient")
            self.LogConsole("Error invalid recipient")

        if self.Port == None or not len(self.Port):
            self.LogErrorLine("Error invalid port")
            self.LogConsole("Error invalid port")
            return

        if self.Rate == None or self.Rate <= 0:
            self.LogErrorLine("Error invalid rate")
            self.LogConsole("Error invalid rate")
            return

        # rate * 10 bits then convert to MS
        self.CharacterTimeMS = (((1/ self.Rate) * 10)  *1000)

        self.InitComplete = False

        try:
            self.SerialDevice = SerialDevice(port, rate = rate, log = self.log, loglocation = loglocation)
            self.Threads = self.MergeDicts(self.Threads, self.SerialDevice.Threads)

            self.Threads["SendMessageThread"] = MyThread(self.SendMessageThread, Name = "SendMessageThread")

        except Exception as e1:
            self.LogErrorLine("Error opening serial device in MyModem: " + str(e1))

    #------------MyModem::GetConfig---------------------------------------------
    def GetConfig(self):
        return self.config

    #------------MyModem::SendMessageThread-------------------------------------
    def SendMessageThread(self):

        # once SendMessage is called messages are queued and then sent from this thread
        time.sleep(0.5)
        while True:
            try:
                self.SendActive = False
                if self.WaitForExit("SendMessageThread", 2 ):
                    return

                while self.SendQueue != []:
                    SendError = False

                    if not self.InitComplete:
                        if self.WaitForExit("SendMessageThread", 5 ):
                            return
                        else:
                            continue

                    self.SendActive = True
                    MessageItems = self.SendQueue.pop()
                    try:
                        if not (self.SendSMS(message = MessageItems[0], recipient = MessageItems[1] , msgtype = MessageItems[2])):
                            self.LogError("Error in SendMessageThread, SendSMS failed, retrying")
                            SendError = True
                    except Exception as e1:
                        # put the time back at the end of the queue
                        self.LogErrorLine("Error in SendMessageThread, retrying (2): " + str(e1))
                        SendError = True

                    if SendError:
                        self.SendQueue.insert(len(self.SendQueue),MessageItems)
                        self.SendActive = False
                        # sleep for 10 sec and try again
                        if self.WaitForExit("SendMessageThread", 10 ):
                            return
            except Exception as e1:
                self.LogErrorLine("Error in SendMessageThread: " + str(e1))

    #------------MyModem::SendMessage-------------------------------------------
    # msgtype must be one of "outage", "error", "warn", "info"
    def SendMessage(self, message = None, recipient = None, msgtype = "error"):

        try:
            self.SendQueue.insert(0,[message, recipient, msgtype])
            return True
        except Exception as e1:
            self.LogErrorLine("Error in SendMessage: " + str(e1))
            return False

    #------------------MyModem::MessagesPending---------------------------------
    def MessagesPending(self):

        return self.SendQueue != [] or self.SendActive

    #------------------MyModem::SendCommand-------------------------------------
    def SendATCommand(self, command, response = None, retries = 1, NoAddCRLF = False):

        try:

            if self.SerialDevice == None:
                self.LogError("Serial device is not open!")
                return False

            if not NoAddCRLF:
                command = str(command) + str("\r\n")
                if response != None:
                    response = str(response) + str("\r\n")
            else:
                command = str(command)
                if response != None:
                    response = str(response)
            with self.ModemLock:
                self.Sending = True
                self.SerialDevice.Flush()

                attempts = retries
                while  self.Sending and attempts >= 0:
                    self.SerialDevice.Write(command.encode())
                    if self.LogAtCommands:
                        self.LogError("->" + command)
                    time.sleep(0.75)
                    if None != response:
                        SerialBuffer = self.SerialDevice.GetRxBufferAsString()

                        if self.LogAtCommands and len(SerialBuffer):
                            self.LogError("<-" + SerialBuffer)

                        if SerialBuffer.find("ERROR") >= 0:
                            self.Sending = False
                            self.LogError("Error returned SendATCommand: CMD: " + str(command))
                            return False

                        if SerialBuffer.find(response) >= 0:
                            self.Sending = False
                            attempts += 1

                    elif None == response:
                        self.Sending = False

                    if self.Sending:
                        time.sleep(0.5)
                        attempts = attempts - 1
                    else:
                        break

                return (attempts >= 0)

        except Exception as e1:
            self.LogErrorLine("Error in SendATCommand: " + "CMD: " + str(command) + " : "+ str(e1))
            return False

    #------------------MyModem::Send--------------------------------------------
    def SendSMS(self, message = None, recipient = None, msgtype = "error"):

        try:

            if recipient == None:
                recipient = self.Recipient

            if recipient == None or not len(recipient):
                self.LogError("Invalid recipient in SendSMS.")
                return False

            with self.ModemLock:
                # set default config
                if not self.SendATCommand("ATZ", "OK"):
                    self.LogError("Failed setting default config in MySMS:Send")
                    self.Errors += 1

                # set text message mode
                if not self.SendATCommand("AT+CMGF=1", "OK"):
                    self.LogError("Failed setting message mode in MySMS:Send")
                    self.Errors += 1
                    return False

                StartMessage = str("AT+CMGS=" + '"' + str(recipient) + '"' + "\r")
                if not self.SendATCommand(StartMessage, ">", retries = 0,  NoAddCRLF = True):
                    self.SendATCommand("\x1b" , "OK", retries = 1,  NoAddCRLF = True)
                    self.LogError("Failed sending CMGS in MySMS:Send")
                    self.Errors += 1
                    return False

                if not self.SendATCommand(str(message) + "\r" , ">", retries = 0,  NoAddCRLF = True):
                    self.SendATCommand("\x1b" , "OK", retries = 1,  NoAddCRLF = True)
                    self.LogError("Failed sending Message Body in MySMS:Send")
                    self.Errors += 1
                    return False

                if not self.SendATCommand("\x1a" , "OK", retries = 1,  NoAddCRLF = True):
                    self.SendATCommand("\x1b" , "OK", retries = 1,  NoAddCRLF = True)
                    self.LogError("Failed sending EOM in MySMS:Send")
                    self.Errors += 1
                    return False

                self.SendATCommand("AT", 'OK')
                self.MessagesSent += 1
            return True
        except Exception as e1:
            self.Errors += 1
            self.LogErrorLine("Error in MySMS:Send: " + str(e1))
            return False
    #------------------MyModem::GetQuotedString---------------------------------
    def GetQuotedString(self, InputString):

        try:
            quoted = re.compile('"[^"]*"')
            for value in quoted.findall(InputString):
                newline = "".join( c for c in value if  c not in '"' )
                return newline

            return None
        except Exception as e1:
            self.LogErrorLine("Error in GetQuotedString: " + str(InputString) + ": " + str(e1))
            return ""
    #------------------MyModem::GetNumbersFromString----------------------------
    def GetNumbersFromString(self, InputString):
        # return list of numbers
        try:
            return re.findall(r'\d+', InputString)
        except Exception as e1:
            self.LogErrorLine("Error in GetNumbersFromString: " + str(InputString) + ": " + str(e1))
            return []

    #------------------MyModem::GetItemsFromCommand-----------------------------
    def GetItemsFromCommand(self, InputString):

        try:
            ReturnString = InputString
            Index = ReturnString.find(":")
            if Index > 1 or len(ReturnString) < 2:
                ListItems = ReturnString[Index + 1 :].split(",")
                ListItems = map(str.strip, ListItems)
                return ListItems
            else:
                return [InputString.split('\r\n')[1].strip()]
        except Exception as e1:
            self.LogErrorLine("Error in GetItemsFromCommand: " + str(InputString) + ": " + str(e1))
            self.LogErrorLine("Input: " + str(InputString))
            return []

    #------------------MyModem::GetInfo-----------------------------------------
    def GetInfo(self, ReturnString = False):
        ModemInfo = collections.OrderedDict()
        try:
            with self.ModemLock:
                ModemInfo["Port"] = str(self.Port)
                ModemInfo["Rate"] = str(self.Rate)
                ModemInfo["Messages Sent"] = str(self.MessagesSent)
                ModemInfo["Errors"] = str(self.Errors)

                # get operator name
                if self.SendATCommand("AT+COPS?","OK" ):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ModemInfo["Carrier"] = self.GetQuotedString(Buffer.split('\r\n')[1])

                # AT+CIMI IMSI (International mobile subscriber identification)
                if self.SendATCommand("AT+CIMI","OK" ):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue):
                        ModemInfo["IMSI"] = ReturnValue[0]

                ## get SIM card state
                if self.SendATCommand("AT+UUICC?", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue):
                        SIMType = self.GetNumbersFromString(ReturnValue[0])[0]
                        if SIMType == "0":
                            ModemInfo["SIM Type"] = "2G"
                        elif SIMType == "1":
                            ModemInfo["SIM Type"] = "3G or 4G"


                # name of manufacturer (AT+CGMI)
                if self.SendATCommand("AT+CGMI", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue):
                        ModemInfo["Manufacturer"] = ReturnValue[0]

                # show module/model name
                if self.SendATCommand("AT+CGMM", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue):
                        ModemInfo["Model"] = ReturnValue[0]

                # phone / modem info

                # IMEI number (International Mobile Equipment Identity) (AT+CGSN)
                if self.SendATCommand("AT+CGSN", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue):
                        ModemInfo["IMEI"] = ReturnValue[0]

                # software version (AT+CGMR)
                if self.SendATCommand("AT+CGMR", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue):
                        ModemInfo["Firmware Version"] = ReturnValue[0]

                ## subscriber info MSISDN (AT+CNUM)
                if self.SendATCommand("AT+CNUM", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    ReturnValue = self.GetItemsFromCommand(Buffer)
                    if len(ReturnValue) >= 2:
                        ModemInfo["MSISDN"] = self.GetQuotedString(ReturnValue[1])

                # mobile phone activity status (AT+CPAS),  returns "+CPAS: 0" where number is:
                #    0: ready (MT allows commands from DTE)
                #    1: unavailable (MT does not allow commands from
                #    2: unknown (MT is not guaranteed to respond to instructions)
                #    3: ringing (MT is ready for commands from DTE, but the ringer is active)
                #    4:callinprogress(MTisreadyforcommandsfromDTE,butacallisinprogress,e.g.callactive,
                #    hold, disconnecting)
                #    5:asleep(MEisunabletoprocesscommandsfromDTEbecauseitisinalowfunctionalitystate)
                if self.SendATCommand("AT+CPAS", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    NumberList = self.GetItemsFromCommand(Buffer)
                    if len(NumberList) >= 1:
                        Status = self.GetNumbersFromString(NumberList[0])[0]
                        if Status == "0":
                            ModemInfo["Status"] = "Ready"
                        elif Status == "1":
                            ModemInfo["Status"] = "Unavailable"
                        elif Status == "2":
                            ModemInfo["Status"] = "Unknown"
                        elif Status == "3":
                            ModemInfo["Status"] = "Ringing"
                        elif Status == "4":
                            ModemInfo["Status"] = "Call In Progress"
                        elif Status == "5":
                            ModemInfo["Status"] = "Asleep"
                        else:
                            ModemInfo["Status"] = "Unknown"

                # mobile network registration status (AT+CREG?) returns "+CREG: 0,0" or +CREG: n,stat
                #    <n>
                #    0 (default value and factory-programmed value): network registration URC disabled
                #    1: network registration URC +CREG: <stat> enabled
                #    2: network registration and location information URC +CREG: <stat>[,<lac>,<ci>[,<AcTStatus>]] enabled
                #    <stat>
                #    0: not registered, the MT is not currently searching a new operator to register to
                #    1: registered, home network
                #    2: not registered, but the MT is currently searching a new operator to register to
                #    3: registration denied
                #    4: unknown (e.g. out of GERAN/UTRAN/E-UTRAN coverage)
                #    5: registered, roaming
                #    6:registeredfor"SMSonly",homenetwork(applicableonlywhen<AcTStatus>indicates E-UTRAN)
                #    7:registeredfor"SMSonly",roaming(applicableonlywhen<AcTStatus>indicatesE-UTRAN)
                #    8: attached for emergency bearer services only
                #    9:registeredfor"CSFBnotpreferred",homenetwork(applicableonlywhen<AcTStatus> indicates E-UTRAN)
                #    10:registeredfor"CSFBnotpreferred",roaming(applicableonlywhen<AcTStatus>indicates E-UTRAN)

                ## AT+REG=2
                self.SendATCommand("AT+CREG=2", "OK")

                ## +CREG, +CEREG and +CGREG.
                if self.SendATCommand("AT+CREG?", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    NumberList = self.GetItemsFromCommand(Buffer)
                    if len(NumberList) >= 1:
                        if NumberList[0] == "0":
                            ModemInfo["Registration Status"] = "Disabled"
                        elif NumberList[0] == "1" or NumberList[0] == "2":
                            ModemInfo["Registration Status"] = "Enabled"
                    if len(NumberList) >= 2:
                        NetworkRegistrationLookup = {
                            "0" : "Not Registered, Not searching",
                            "1" : "Registered, Home network",
                            "2" : "Not Registered, Searching",
                            "3" : "Registration Denied",
                            "4" : "Unknown, Out of Coverage",
                            "5" : "Registered, Roaming",
                            "6" : "Registered, Home network, SMS Only",
                            "7" : "Registered, Roaming, SMS Only",
                            "8" : "Attached for Emergency Bearer Services Only",
                            "9" : "Registered, Home network, CSFB Not Preferred",
                            "10" : "Registered, Roaming, CSFB Not Preferred",
                        }

                        ModemInfo["Network Registration"] = NetworkRegistrationLookup.get(NumberList[1], "Unknown")

                    if len(NumberList) > 5:
                        NetworkTech ={
                            "0" : "GSM",                        # 2G
                            "1" : "GSM Compact",                # 2G
                            "2" : "UTRAN",                      # 3G
                            "3" : "GSM w/EGPRS",                # 2.5G
                            "4" : "UTRAN w/HSDPA",              # 3G
                            "5" : "UTRAN w/HSUPA",              # 3G
                            "6" : "UTRAN w/HSDPA and HSUPA",    # 3G
                            "7" : "E-UTRAN",                    # 4G
                            "255" : "Unknown"
                        }
                        ModemInfo["Cell Network Technology"] = NetworkTech.get(self.GetNumbersFromString(NumberList[4])[0], "Unknown")

                # radio signal strength (AT+CSQ), returns "+CSQ: 2,5"
                # first number is RSSI:
                #    The allowed range is 0-31 and 99. Remapped indication of the following parameters:
                #    the Received Signal Strength Indication (RSSI) in GSM RAT
                #    the Received Signal Code Power (RSCP) in UMTS RAT
                #    the Reference Signal Received Power (RSRP) in LTE RAT
                #    When the RF power level of the received signal is the highest possible, the value 31 is reported. When it is not known, not detectable or currently not available, 99 is returned.
                # second number is signal quality:
                #   The allowed range is 0-7 and 99. The information provided depends on the selected RAT:
                #   In 2G RAT CS dedicated and GPRS packet transfer mode indicates the BitErrorRate(BER)as
                #   specified in 3GPP TS 45.008 [148]
                #   In 2G RAT EGPRS packet transfer mode indicates the Mean BitErrorProbability(BEP) of a radio
                #   block. 3GPP TS 45.008 [148] specifies the range 0-31 for the Mean BEP which is mapped to
                #   the range 0-7 of <qual>
                #   In UMTS RAT indicates the Energy per Chip/Noise(ECN0) ratioin dB levels of the current cell.
                #   3GPP TS 25.133 [106] specifies the range 0-49 for EcN0 which is mapped to the range 0-7
                #   of <qual>
                #   In LTE RAT indicates the Reference Signal Received Quality(RSRQ). TS36.133[105] specifies
                #   the range 0-34 for RSRQ which is mapped to the range 0-7 of <qual>

                if self.SendATCommand("AT+CSQ", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    NumberList = self.GetItemsFromCommand(Buffer)

                    if len(NumberList) >= 2:
                        RSSIList = self.ParseRSSI(NumberList[0])
                        ModemInfo["RSSI"] = RSSIList[0] + " dBm" + ", " + RSSIList[1]
                        ModemInfo["Signal Quality"] = self.GetNumbersFromString(NumberList[1])[0]

                if self.SendATCommand("AT+CCLK?", "OK"):
                    Buffer = self.SerialDevice.GetRxBufferAsString()
                    if len(Buffer):
                        ModemInfo["Network Time"] = self.GetQuotedString(Buffer)


        except Exception as e1:
            self.LogErrorLine("Error in MyModem:GetInfo: " + str(e1))

        if ReturnString:
            return self.DictToString(ModemInfo)
        return ModemInfo
    #------------------MyModem::ParseRSSI-------------------------------------------
    def ParseRSSI(self, Value):

        RSSILookup = {
            "0" : ["-113", "Poor"],
            "1" : ["-111", "Poor"],
            "2" : ["-109", "Marginal"],
            "3" : ["-107", "Marginal"],
            "4" : ["-105", "Marginal"],
            "5" : ["-103", "Marginal"],
            "6" : ["-101", "Marginal"],
            "7" : ["-99", "Marginal"],
            "8" : ["-97", "Marginal"],
            "9" : ["-95", "Marginal"],
            "10" : ["-93", "OK"],
            "11" : ["-91", "OK"],
            "12" : ["-89", "OK"],
            "13" : ["-87", "OK"],
            "14" : ["-85", "OK"],
            "15" : ["-83", "Good"],
            "16" : ["-81", "Good"],
            "17" : ["-79", "Good"],
            "18" : ["-77", "Good"],
            "19" : ["-75", "Good"],
            "20" : ["-73", "Excellent"],
            "21" : ["-71", "Excellent"],
            "22" : ["-69", "Excellent"],
            "23" : ["-67", "Excellent"],
            "24" : ["-65", "Excellent"],
            "25" : ["-63", "Excellent"],
            "26" : ["-61", "Excellent"],
            "27" : ["-59", "Excellent"],
            "28" : ["-57", "Excellent"],
            "29" : ["-55", "Excellent"],
            "30" : ["-53", "Excellent"],
            "99" : ["Unknown", "Unknown"]
            }

        return RSSILookup.get(Value, ["Unknown", "Unknown"])

    #------------------MyModem::Close-------------------------------------------
    def Close(self):
        try:
            try:
                self.KillThread("SendMessageThread")
            except:
                pass
            try:
                self.SerialDevice.Close()
            except:
                pass
        except Exception as e1:
            self.LogErrorLine("Error Closing Modem: " + str(e1))
Exemplo n.º 2
0
class ModbusProtocol(ModbusBase):

    def __init__(self,
        updatecallback,
        address = 0x9d,
        name = "/dev/serial0",
        rate = 9600,
        Parity = None,
        OnePointFiveStopBits = None,
        config = None,
        host = None,
        port = None,
        modbustcp = False):     # True of Modbus TCP, else if TCP then assume serial over TCP (Modbus RTU over serial)

        super(ModbusProtocol, self).__init__(updatecallback = updatecallback, address = address, name = name, rate = rate, config = config)

        try:
            if config == None:
                self.ModbusTCP = modbustcp
                self.Host = host
                self.Port = port
            self.TransactionID = 0
            self.AlternateFileProtocol = False

            if host != None and port != None and self.config == None:
                # in this instance we do not use a config file, but config comes from command line
                self.UseTCP = True

            # ~3000 for 9600               bit time * 10 bits * 10 char * 2 packets + wait time(3000) (convert to ms * 1000)
            self.ModBusPacketTimoutMS = (((((1/float(self.Rate)) * 10.0) * 10.0 * 2.0) *1000.0)  + 3000.0)     # .00208

            self.ModBusPacketTimoutMS += self.AdditionalModbusTimeout * 1000.0

            if self.ModbusTCP:
                self.MIN_PACKET_RESPONSE_LENGTH -= 2
                self.MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH -= 2
                self.MBUS_FILE_READ_PAYLOAD_SIZE_MINUS_LENGTH -= 2
                self.MBUS_CRC_SIZE = 0
                self.MIN_PACKET_ERR_LENGTH -= 2

            if self.UseTCP:
                self.ModBusPacketTimoutMS = self.ModBusPacketTimoutMS + 2000
            #Starting serial connection
            if self.UseTCP:
                self.Slave = SerialTCPDevice(config = self.config, host = host, port = port)
            else:
                self.Slave = SerialDevice(name = name, rate = rate, Parity = Parity, OnePointFiveStopBits = OnePointFiveStopBits, config = self.config)
            self.Threads = self.MergeDicts(self.Threads, self.Slave.Threads)


        except Exception as e1:
            self.LogErrorLine("Error opening modbus device: " + str(e1))
            self.FatalError("Error opening modbus device.")

        try:
            # CRCMOD library, used for CRC calculations
            self.ModbusCrc = crcmod.predefined.mkCrcFun('modbus')
            self.InitComplete = True
        except Exception as e1:
            self.FatalError("Unable to find crcmod package: " + str(e1))

    #--------------------ModbusProtocol:GetExceptionString----------------------
    def GetExceptionString(self, Code):

        try:

            LookUp = {
                self.MBUS_EXCEP_FUNCTION : "Illegal Function",
                self.MBUS_EXCEP_ADDRESS : "Illegal Address",
                self.MBUS_EXCEP_DATA : "Illegal Data Value",
                self.MBUS_EXCEP_SLAVE_FAIL : "Slave Device Failure",
                self.MBUS_EXCEP_ACK : "Acknowledge",
                self.MBUS_EXCEP_BUSY : "Slave Device Busy",
                self.MBUS_EXCEP_NACK : "Negative Acknowledge",
                self.MBUS_EXCEP_MEM_PE : "Memory Parity Error",
                self.MBUS_EXCEP_GATEWAY : "Gateway Path Unavailable",
                self.MBUS_EXCEP_GATEWAY_TG : "Gateway Target Device Failed to Respond"
             }

            if Code == self.MBUS_EXCEP_FUNCTION:
                 self.ExcepFunction += 1
            elif Code == self.MBUS_EXCEP_ADDRESS:
                self.ExcepAddress += 1
            elif Code == self.MBUS_EXCEP_DATA:
                self.ExcepData += 1
            elif Code == self.MBUS_EXCEP_SLAVE_FAIL:
                self.ExcepSlave += 1
            elif Code == self.MBUS_EXCEP_ACK:
                self.ExcepAck += 1
            elif Code == self.MBUS_EXCEP_BUSY:
                self.ExcepBusy += 1
            elif Code == self.MBUS_EXCEP_NACK:
                self.ExcepNack += 1
            elif Code == self.MBUS_EXCEP_MEM_PE:
                self.ExcepMemPe += 1
            elif Code == self.MBUS_EXCEP_GATEWAY:
                self.ExcepGateway += 1
            elif Code == self.MBUS_EXCEP_GATEWAY_TG:
                self.ExcepGateWayTg += 1

            ReturnString = LookUp.get(Code, "Unknown")
            ReturnString = ReturnString + (": %02x" % Code)
            return ReturnString
        except Exception as e1:
            self.LogErrorLine("Error in GetExceptionString: " + str(e1))

        return ""
    # ---------- ModbusProtocol::CheckResponseAddress---------------------------
    def CheckResponseAddress(self, Address):

        if Address == self.Address:
            return True
        if self.ResponseAddress == None:
            return False
        if Address == self.ResponseAddress:
            return True
        return False

    # ---------- ModbusProtocol::GetPacketFromSlave-----------------------------
    #  This function returns two values, the first is boolean. The seconds is
    #  a packet (list). If the return value is True and an empty packet, then
    #  keep looking because the data has not arrived yet, if return is False there
    #  is and error. If True and a non empty packet then it is valid data
    def GetPacketFromSlave(self, min_response_override = None):

        LocalErrorCount = 0
        Packet = []
        EmptyPacket = []    # empty packet
        try:
            if not len(self.Slave.Buffer):
                return True, EmptyPacket

            if self.ModbusTCP:
                # byte 0:   transaction identifier - copied by server - usually 0
                # byte 1:   transaction identifier - copied by server - usually 0
                # byte 2:   protocol identifier = 0
                # byte 3:   protocol identifier = 0
                # byte 4:   length field (upper byte) = 0 (since all messages are smaller than 256)
                # byte 5:   length field (lower byte) = number of bytes following
                # byte 6:   unit identifier (previously 'slave address')
                # byte 7:   MODBUS function code
                # byte 8 on:    data as needed


                if len(self.Slave.Buffer) < (self.MIN_PACKET_ERR_LENGTH + self.MODBUS_TCP_HEADER_SIZE):
                    return True, EmptyPacket

                # transaction ID must match
                rxID = ((self.Slave.Buffer[0] <<8) | (self.Slave.Buffer[1] & 0xFF))
                if self.CurrentTransactionID != rxID:
                    self.LogError("ModbusTCP transaction ID mismatch: %x %x" % (self.CurrentTransactionID, rxID))
                    self.DiscardByte(reason = "Transaction ID")
                    self.Flush()
                    return False, EmptyPacket
                # protocol ID is zero
                if self.Slave.Buffer[2] != 0 or self.Slave.Buffer[3] != 0:
                    self.LogError("ModbusTCP protocool ID non zero: %x %x" % (self.Slave.Buffer[2], self.Slave.Buffer[3]))
                    self.DiscardByte(reason = "protocol error")
                    self.Flush()
                    return False, EmptyPacket
                # Modbus TCP payload length
                ModbusTCPLength = ((self.Slave.Buffer[4] <<8) | (self.Slave.Buffer[5] & 0xFF))
                if len(self.Slave.Buffer[6:]) != ModbusTCPLength:
                    # more data is needed
                    return True, EmptyPacket

                # remove modbud TCP header
                for i in range(0, self.MODBUS_TCP_HEADER_SIZE):
                    self.Slave.Buffer.pop(0)

            if not self.CheckResponseAddress(self.Slave.Buffer[self.MBUS_OFF_ADDRESS]):
                self.DiscardByte(reason = "Response Address")
                self.Flush()
                return False, EmptyPacket

            if len(self.Slave.Buffer) < self.MIN_PACKET_ERR_LENGTH:
                return True, EmptyPacket   # No full packet ready

            if self.Slave.Buffer[self.MBUS_OFF_COMMAND] & self.MBUS_ERROR_BIT:
                for i in range(0, self.MIN_PACKET_ERR_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Excetion code, and CRC
                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    self.ModbusException += 1
                    self.LogError("Modbus Exception: " + self.GetExceptionString(Packet[self.MBUS_OFF_EXCEPTION]) + " , Modbus Command: " + ("%02x" % Packet[self.MBUS_OFF_COMMAND]))
                else:
                    self.CrcError += 1
                return False, Packet

            if min_response_override != None:
                if len(self.Slave.Buffer) < min_response_override:
                    return True, EmptyPacket   # No full packet ready
            else:
                if len(self.Slave.Buffer) < self.MIN_PACKET_RESPONSE_LENGTH:
                    return True, EmptyPacket   # No full packet ready

            if  self.Slave.Buffer[self.MBUS_OFF_COMMAND] in [self.MBUS_CMD_READ_REGS]:
                # it must be a read command response
                length = self.Slave.Buffer[self.MBUS_OFF_RESPONSE_LEN]   # our packet tells us the length of the payload
                # if the full length of the packet has not arrived, return and try again
                if (length + self.MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH) > len(self.Slave.Buffer):
                    return True, EmptyPacket

                for i in range(0, length + self.MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Length, message and CRC

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packet
            elif self.Slave.Buffer[self.MBUS_OFF_COMMAND] in [self.MBUS_CMD_WRITE_REGS]:
                # it must be a write command response
                if len(self.Slave.Buffer) < self.MIN_PACKET_MIN_WRITE_RESPONSE_LENGTH:
                    return True, EmptyPacket
                for i in range(0, self.MIN_PACKET_MIN_WRITE_RESPONSE_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))    # address, function, address hi, address low, quantity hi, quantity low, CRC high, crc low

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packet
            elif self.Slave.Buffer[self.MBUS_OFF_COMMAND] in [self.MBUS_CMD_READ_FILE]:
                length = self.Slave.Buffer[self.MBUS_OFF_RESPONSE_LEN]   # our packet tells us the length of the payload
                if self.Slave.Buffer[self.MBUS_OFF_FILE_TYPE] != self.MBUS_FILE_TYPE_VALUE:
                    self.LogError("Invalid modbus file record type")
                    self.ComValidationError += 1
                    return False, EmptyPacket
                # if the full length of the packet has not arrived, return and try again
                if (length + self.MBUS_FILE_READ_PAYLOAD_SIZE_MINUS_LENGTH) > len(self.Slave.Buffer):
                    return True, EmptyPacket
                # we will copy the entire buffer, this will be validated at a later time
                for i in range(0, len(self.Slave.Buffer)):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Length, message and CRC

                if len(self.Slave.Buffer):
                    self.LogHexList(self.Slave.Buffer, prefix = "Left Over")

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packet
            elif self.Slave.Buffer[self.MBUS_OFF_COMMAND] in [self.MBUS_CMD_WRITE_FILE]:
                length = self.Slave.Buffer[self.MBUS_OFF_RESPONSE_LEN]   # our packet tells us the length of the payload
                if self.Slave.Buffer[self.MBUS_OFF_WRITE_FILE_TYPE] != self.MBUS_FILE_TYPE_VALUE:
                    self.LogError("Invalid modbus write file record type")
                    self.ComValidationError += 1
                    return False, EmptyPacket
                # if the full length of the packet has not arrived, return and try again
                if (length + self.MBUS_FILE_READ_PAYLOAD_SIZE_MINUS_LENGTH) > len(self.Slave.Buffer):
                    return True, EmptyPacket
                # we will copy the entire buffer, this will be validated at a later time
                for i in range(0, len(self.Slave.Buffer)):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Length, message and CRC

                if len(self.Slave.Buffer):
                    self.LogHexList(self.Slave.Buffer, prefix = "Left Over")

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packe
            else:
                # received a  response to a command we do not support
                self.DiscardByte(reason = "Invalid Modbus command")
                self.Flush()
                return False, EmptyPacket
        except Exception as e1:
            self.LogErrorLine("Error in GetPacketFromSlave: " + str(e1))
            self.ComValidationError += 1
            return False, EmptyPacket
    # ---------- GeneratorDevice::DiscardByte-----------------------------------
    def DiscardByte(self, reason = None):

        discard = self.Slave.DiscardByte()
        if reason == None:
            reason = "Unknown"
        self.LogError("Discarding byte slave: %02x : %s " % (discard, str(reason)))

    #-------------ModbusProtocol::PWT-------------------------------------------
    # called from derived calls to get to overridded function ProcessWriteTransaction
    def _PWT(self, Register, Length, Data, min_response_override = None):

        try:
            with self.CommAccessLock:
                MasterPacket = []

                MasterPacket = self.CreateMasterPacket(Register, length = int(Length), command = self.MBUS_CMD_WRITE_REGS, data = Data)

                if len(MasterPacket) == 0:
                    return False

                #skipupdate=True to skip writing results to cached reg values
                return self.ProcessOneTransaction(MasterPacket, skipupdate = True, min_response_override = min_response_override)
        except Exception as e1:
            self.LogErrorLine("Error in ProcessWriteTransaction: " + str(e1))
            return False

    #-------------ModbusProtocol::ProcessWriteTransaction-----------------------
    def ProcessWriteTransaction(self, Register, Length, Data):
        return self._PWT(Register, Length, Data)

    #-------------ModbusProtocol::PT--------------------------------------------
    # called from derived calls to get to overridded function ProcessTransaction
    def _PT(self, Register, Length, skipupdate = False, ReturnString = False):

        MasterPacket = []

        try:
            with self.CommAccessLock:
                MasterPacket = self.CreateMasterPacket(Register, length = int(Length))

                if len(MasterPacket) == 0:
                    return ""

                return self.ProcessOneTransaction(MasterPacket, skipupdate = skipupdate, ReturnString = ReturnString)     # don't log

        except Exception as e1:
            self.LogErrorLine("Error in ProcessTransaction: " + str(e1))
            return ""

    #-------------ModbusProtocol::ProcessTransaction----------------------------
    def ProcessTransaction(self, Register, Length, skipupdate = False, ReturnString = False):
        return self._PT(Register, Length, skipupdate, ReturnString)

    #-------------ModbusProtocol::ProcessFileReadTransaction--------------------
    def ProcessFileReadTransaction(self, Register, Length, skipupdate = False, file_num = 1, ReturnString = False):

        MasterPacket = []

        try:
            with self.CommAccessLock:
                MasterPacket = self.CreateMasterPacket(Register, length = int(Length), command = self.MBUS_CMD_READ_FILE, file_num = file_num)

                if len(MasterPacket) == 0:
                    return ""

                return self.ProcessOneTransaction(MasterPacket, skipupdate = skipupdate, ReturnString = ReturnString)     # don't log

        except Exception as e1:
            self.LogErrorLine("Error in ProcessFileReadTransaction: " + str(e1))
            return ""

    #-------------ModbusProtocol::ProcessFileWriteTransaction-------------------
    def ProcessFileWriteTransaction(self, Register, Length, Data, file_num = 1, min_response_override = None):

        MasterPacket = []

        try:
            with self.CommAccessLock:
                MasterPacket = self.CreateMasterPacket(Register, length = int(Length), command = self.MBUS_CMD_WRITE_FILE, file_num = file_num, data = Data)

                if len(MasterPacket) == 0:
                    return ""

                #skipupdate=True to skip writing results to cached reg values
                return self.ProcessOneTransaction(MasterPacket, skipupdate = True, min_response_override = min_response_override)

        except Exception as e1:
            self.LogErrorLine("Error in ProcessFileWriteTransaction: " + str(e1))
            return ""

    #------------ModbusProtocol::ProcessOneTransaction--------------------------
    def ProcessOneTransaction(self, MasterPacket, skipupdate = False, ReturnString = False, min_response_override = None):

        try:
            if self.ModbusTCP:
                PacketOffset = self.MODBUS_TCP_HEADER_SIZE
            else:
                PacketOffset = 0

            with self.CommAccessLock:       # this lock should allow calls from multiple threads

                if len(self.Slave.Buffer):
                    self.UnexpectedData += 1
                    self.LogError("Flushing, unexpected data. Likely timeout.")
                    self.Flush()
                self.SendPacketAsMaster(MasterPacket)

                SentTime = datetime.datetime.now()
                while True:
                    # be kind to other processes, we know we are going to have to wait for the packet to arrive
                    # so let's sleep for a bit before we start polling
                    if self.SlowCPUOptimization:
                        time.sleep(0.03)
                    else:
                        time.sleep(0.01)

                    if self.IsStopping:
                        return ""
                    RetVal, SlavePacket = self.GetPacketFromSlave(min_response_override = min_response_override)

                    if RetVal == True and len(SlavePacket) != 0:    # we receive a packet
                        self.TotalElapsedPacketeTime += (self.MillisecondsElapsed(SentTime) / 1000)
                        break
                    if RetVal == False:
                        self.LogError("Error Receiving slave packet for register %04x" % (self.GetRegisterFromPacket(MasterPacket, offset = PacketOffset) ))
                        # Errors returned here are logged in GetPacketFromSlave
                        time.sleep(1)
                        self.Flush()
                        return ""

                    msElapsed = self.MillisecondsElapsed(SentTime)
                    # This normally takes about 30 ms however in some instances it can take up to 950ms
                    # the theory is this is either a delay due to how python does threading, or
                    # delay caused by the generator controller.
                    # each char time is about 1 millisecond (at 9600 baud) so assuming a 10 byte packet
                    # transmitted and a 10 byte received with about 5 char times of silence
                    # in between should give us about 25ms
                    if msElapsed > self.ModBusPacketTimoutMS:
                        self.ComTimoutError += 1
                        self.LogError("Error: timeout receiving slave packet for register %04x Buffer: %d" % (self.GetRegisterFromPacket(MasterPacket, offset = PacketOffset), len(self.Slave.Buffer)) )
                        if len(self.Slave.Buffer):
                            self.LogHexList(self.Slave.Buffer, prefix = "Buffer")
                        self.Flush()
                        return ""

                # update our cached register dict
                ReturnRegValue = self.UpdateRegistersFromPacket(MasterPacket, SlavePacket, SkipUpdate = skipupdate, ReturnString = ReturnString)
                if ReturnRegValue == "Error":
                    self.LogHexList(MasterPacket, prefix = "Master")
                    self.LogHexList(SlavePacket, prefix = "Slave")
                    self.ComValidationError += 1
                    self.Flush()
                    ReturnRegValue = ""

            return ReturnRegValue


        except Exception as e1:
            self.LogErrorLine("Error in ProcessOneTransaction: " + str(e1))
            return ""

    # ---------- ModbusProtocol::MillisecondsElapsed----------------------------
    def MillisecondsElapsed(self, ReferenceTime):

        CurrentTime = datetime.datetime.now()
        Delta = CurrentTime - ReferenceTime
        return Delta.total_seconds() * 1000

    #------------GetRegisterFromPacket -----------------------------------------
    def GetRegisterFromPacket(self, Packet, offset = 0):
        try:
            Register = 0

            if Packet[self.MBUS_OFF_COMMAND + offset] in [self.MBUS_CMD_READ_FILE,self.MBUS_CMD_WRITE_FILE] :
                Register = Packet[self.MBUS_OFF_FILE_RECORD_HI + offset] << 8 |  Packet[self.MBUS_OFF_FILE_RECORD_LOW + offset] & 0x00FF
            else:
                Register = Packet[self.MBUS_OFF_REGISTER_HI + offset] << 8 |  Packet[self.MBUS_OFF_REGISTER_LOW + offset] & 0x00FF
            return Register
        except Exception as e1:
            self.LogErrorLine("Error in GetRegisterFromPacket: " + str(e1))
            return Register

    # ---------- ModbusProtocol::CreateMasterPacket-----------------------------
    # the length is the register length in words, as required by modbus
    # build Packet
    def CreateMasterPacket(self, register, length = 1, command = 0x03, data=[], file_num = 1):

        Packet = []
        try:
            RegisterInt = int(register,16)

            if RegisterInt < self.MIN_REGISTER or RegisterInt > self.MAX_REGISTER:
                self.ComValidationError += 1
                self.LogError("Validation Error: CreateMasterPacket maximum regiseter value exceeded: " + str(register))
                return []
            if file_num < self.MIN_FILE_NUMBER or file_num > self.MAX_FILE_NUMBER:
                self.ComValidationError += 1
                self.LogError("Validation Error: CreateMasterPacket maximum file number value exceeded: " + str(file_num))
                return []

            if command == self.MBUS_CMD_READ_REGS:
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(RegisterInt >> 8)             # reg high
                Packet.append(RegisterInt & 0x00FF)         # reg low
                Packet.append(length >> 8)                  # length high
                Packet.append(length & 0x00FF)              # length low
                CRCValue = self.GetCRC(Packet)
                if CRCValue != None:
                    Packet.append(CRCValue & 0x00FF)            # CRC low
                    Packet.append(CRCValue >> 8)                # CRC high

            elif command == self.MBUS_CMD_WRITE_REGS:
                if len(data) == 0:
                    self.LogError("Validation Error: CreateMasterPacket invalid length (1) %x %x" % (len(data), length))
                    self.ComValidationError += 1
                    return []
                if len(data)/2 != length:
                    self.LogError("Validation Error: CreateMasterPacket invalid length (2) %x %x" % (len(data), length))
                    self.ComValidationError += 1
                    return []
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(RegisterInt >> 8)             # reg higy
                Packet.append(RegisterInt & 0x00FF)         # reg low
                Packet.append(length >> 8)                  # Num of Reg higy
                Packet.append(length & 0x00FF)              # Num of Reg low
                Packet.append(len(data))                    # byte count
                for b in range(0, len(data)):
                    Packet.append(data[b])                  # data
                CRCValue = self.GetCRC(Packet)
                if CRCValue != None:
                    Packet.append(CRCValue & 0x00FF)            # CRC low
                    Packet.append(CRCValue >> 8)                # CRC high

            elif command == self.MBUS_CMD_READ_FILE:

                # Note, we only support one sub request at at time
                if RegisterInt < self.MIN_FILE_RECORD_NUM or RegisterInt > self.MAX_FILE_RECORD_NUM:
                    self.ComValidationError += 1
                    self.LogError("Validation Error: CreateMasterPacket maximum regiseter (record number) value exceeded: " + str(register))
                    return []
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(self.MBUS_READ_FILE_REQUEST_PAYLOAD_LENGTH)     # Byte count
                Packet.append(self.MBUS_FILE_TYPE_VALUE)    # always same value
                Packet.append(file_num >> 8)                # File Number hi
                Packet.append(file_num & 0x00FF)            # File Number low
                Packet.append(RegisterInt >> 8)             # register (file record number) high
                Packet.append(RegisterInt & 0x00FF)         # register (file record number) low
                Packet.append(length >> 8)                  # Length to return hi
                Packet.append(length & 0x00FF)              # Length to return lo
                CRCValue = self.GetCRC(Packet)
                if CRCValue != None:
                    Packet.append(CRCValue & 0x00FF)            # CRC low
                    Packet.append(CRCValue >> 8)                # CRC high
            elif command == self.MBUS_CMD_WRITE_FILE:
                # Note, we only support one sub request at at time
                if RegisterInt < self.MIN_FILE_RECORD_NUM or RegisterInt > self.MAX_FILE_RECORD_NUM:
                    self.ComValidationError += 1
                    self.LogError("Validation Error: CreateMasterPacket maximum regiseter (write record number) value exceeded: " + str(register))
                    return []
                if len(data)/2 != length:
                    self.LogError("Validation Error: CreateMasterPacket invalid length (3) %x %x" % (len(data), length))
                    self.ComValidationError += 1
                    return []
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(length * 2 + self.MBUS_FILE_WRITE_REQ_SIZE_MINUS_LENGTH)  # packet payload size from here
                Packet.append(self.MBUS_FILE_TYPE_VALUE)    # always same value
                Packet.append(file_num >> 8)                # File Number hi
                Packet.append(file_num & 0x00FF)            # File Number low
                Packet.append(RegisterInt >> 8)             # register (file record number) high
                Packet.append(RegisterInt & 0x00FF)         # register (file record number) low
                Packet.append(length >> 8)                  # Length to return hi
                Packet.append(length & 0x00FF)              # Length to return lo
                for b in range(0, len(data)):
                    Packet.append(data[b])                  # data
                CRCValue = self.GetCRC(Packet)
                if CRCValue != None:
                    Packet.append(CRCValue & 0x00FF)            # CRC low
                    Packet.append(CRCValue >> 8)                # CRC high

            else:
                self.LogError("Validation Error: Invalid command in CreateMasterPacket!")
                self.ComValidationError += 1
                return []
        except Exception as e1:
            self.LogErrorLine("Error in CreateMasterPacket: " + str(e1))

        if len(Packet) > self.MAX_MODBUS_PACKET_SIZE:
            self.LogError("Validation Error: CreateMasterPacket: Packet size exceeds max size")
            self.ComValidationError += 1
            return []

        if self.ModbusTCP:
            return self.ConvertToModbusModbusTCP(Packet)
        return Packet

    #-------------ModbusProtocol::GetTransactionID------------------------------
    def GetTransactionID(self):

        ID = self.TransactionID
        self.CurrentTransactionID = ID
        self.TransactionID += 1
        if self.TransactionID > 0xffff:
            self.TransactionID = 0

        return ID

    #-------------ModbusProtocol::ConvertToModbusModbusTCP----------------------
    def ConvertToModbusModbusTCP(self, Packet):

        # byte 0:   transaction identifier - copied by server - usually 0
        # byte 1:   transaction identifier - copied by server - usually 0
        # byte 2:   protocol identifier = 0
        # byte 3:   protocol identifier = 0
        # byte 4:   length field (upper byte) = 0 (since all messages are smaller than 256)
        # byte 5:   length field (lower byte) = number of bytes following
        # byte 6:   unit identifier (previously 'slave address')
        # byte 7:   MODBUS function code
        # byte 8 on:    data as needed
        try:
            if not self.ModbusTCP:
                return Packet
            # remove last two bytes of CRC
            Packet.pop()
            Packet.pop()
            length = len(Packet)
            # byte 6 (slave address) is already provided in the Packet argument
            Packet.insert(0,length & 0xff)            # byte 5:   length field (lower byte) = number of bytes following
            Packet.insert(0,length & 0xff00)          # byte 4:   length field (upper byte) = 0 (since all messages are smaller than 256)
            Packet.insert(0,0)                        # byte 3:   protocol identifier = 0
            Packet.insert(0,0)                        # byte 2:   protocol identifier = 0
            TransactionID = self.GetTransactionID()
            Packet.insert(0, TransactionID & 0xff)      # byte 1:   transaction identifier (low)- copied by server - usually 0
            Packet.insert(0, (TransactionID & 0xff00) >> 8)    # byte 0:   transaction identifier (high)- copied by server - usually 0
            return Packet
        except Exception as e1:
            self.LogErrorLine("Error in CreateModbusTCPHeader: " + str(e1))
            return []

    #-------------ModbusProtocol::SendPacketAsMaster----------------------------
    def SendPacketAsMaster(self, Packet):

        try:
            ByteArray = bytearray(Packet)
            self.Slave.Write(ByteArray)
            self.TxPacketCount += 1
        except Exception as e1:
            self.LogErrorLine("Error in SendPacketAsMaster: " + str(e1))
            self.LogHexList(Packet, prefix = "Packet")

    # ---------- ModbusProtocol::UpdateRegistersFromPacket----------------------
    #    Update our internal register list based on the request/response packet
    def UpdateRegistersFromPacket(self, MasterPacket, SlavePacket, SkipUpdate = False, ReturnString = False):
        return self._URFP(MasterPacket, SlavePacket, SkipUpdate, ReturnString)

    # ---------- ModbusProtocol::URFP-------------------------------------------
    #    Called from UpdateRegistersFromPacket
    def _URFP(self, MasterPacket, SlavePacket, SkipUpdate = False, ReturnString = False):

        try:

            if len(MasterPacket) < self.MIN_PACKET_RESPONSE_LENGTH or len(SlavePacket) < self.MIN_PACKET_RESPONSE_LENGTH:
                self.LogError("Validation Error, length: Master" + str(len(MasterPacket)) + " Slave: " + str(len(SlavePacket)))
                return "Error"

            if self.ModbusTCP:
                PacketOffset = self.MODBUS_TCP_HEADER_SIZE
            else:
                PacketOffset = 0
            if MasterPacket[self.MBUS_OFF_ADDRESS + PacketOffset] != self.Address:
                self.LogError("Validation Error: Invalid address in UpdateRegistersFromPacket (Master)")
                return "Error"
            if not self.CheckResponseAddress(SlavePacket[self.MBUS_OFF_ADDRESS]):
                self.LogError("Validation Error: Invalid address in UpdateRegistersFromPacket (Slave)")
                return "Error"
            if not SlavePacket[self.MBUS_OFF_COMMAND] in [self.MBUS_CMD_READ_REGS, self.MBUS_CMD_WRITE_REGS, self.MBUS_CMD_READ_FILE, self.MBUS_CMD_WRITE_FILE]:
                self.LogError("Validation Error: Unknown Function slave %02x %02x" %  (SlavePacket[self.MBUS_OFF_ADDRESS],SlavePacket[self.MBUS_OFF_COMMAND]))
                return "Error"
            if not MasterPacket[self.MBUS_OFF_COMMAND + PacketOffset] in [self.MBUS_CMD_READ_REGS, self.MBUS_CMD_WRITE_REGS, self.MBUS_CMD_READ_FILE, self.MBUS_CMD_WRITE_FILE]:
                self.LogError("Validation Error: Unknown Function master %02x %02x" %  (MasterPacket[self.MBUS_OFF_ADDRESS],MasterPacket[self.MBUS_OFF_COMMAND]))
                return "Error"

            if MasterPacket[self.MBUS_OFF_COMMAND + PacketOffset] != SlavePacket[self.MBUS_OFF_COMMAND]:
                self.LogError("Validation Error: Command Mismatch :" + str(MasterPacket[self.MBUS_OFF_COMMAND]) + ":" + str(SlavePacket[self.MBUS_OFF_COMMAND]))
                return "Error"

             # get register from master packet
            Register = "%04x" % (self.GetRegisterFromPacket(MasterPacket, offset = PacketOffset))

            if MasterPacket[self.MBUS_OFF_COMMAND + PacketOffset] in [self.MBUS_CMD_WRITE_REGS, self.MBUS_CMD_WRITE_FILE]:
                # get register from slave packet
                SlaveRegister = "%04x" % (self.GetRegisterFromPacket(SlavePacket))
                if SlaveRegister != Register:
                    self.LogError("Validation Error: Master Slave Register Mismatch : " + Register +  ":" + SlaveRegister)
                    return "Error"

            RegisterValue = ""
            RegisterStringValue = ""
            if MasterPacket[self.MBUS_OFF_COMMAND + PacketOffset] == self.MBUS_CMD_READ_REGS:
                # get value from slave packet
                length = SlavePacket[self.MBUS_OFF_RESPONSE_LEN]
                if (length + self.MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH) > len(SlavePacket):
                    self.LogError("Validation Error: Slave Length : " + length +  ":" + len(SlavePacket))
                    return "Error"

                for i in range(3, length+3):
                    RegisterValue += "%02x" % SlavePacket[i]
                    if ReturnString:
                        if SlavePacket[i]:
                            RegisterStringValue += chr(SlavePacket[i])
                # update register list
                if not SkipUpdate:
                    if not self.UpdateRegisterList == None:
                        if not ReturnString:
                            if not self.UpdateRegisterList(Register, RegisterValue):
                                self.ComSyncError += 1
                                return "Error"
                        else:
                            if not self.UpdateRegisterList(Register, RegisterStringValue, IsString = True):
                                self.ComSyncError += 1
                                return "Error"

            if MasterPacket[self.MBUS_OFF_COMMAND + PacketOffset] == self.MBUS_CMD_READ_FILE:
                payloadLen = SlavePacket[self.MBUS_OFF_FILE_PAYLOAD_LEN]
                if not self.AlternateFileProtocol:
                    # TODO This is emperical
                    payloadLen -= 1
                for i  in range (self.MBUS_OFF_FILE_PAYLOAD, (self.MBUS_OFF_FILE_PAYLOAD + payloadLen)):
                    RegisterValue += "%02x" % SlavePacket[i]
                    if ReturnString:
                        if SlavePacket[i]:
                            RegisterStringValue += chr(SlavePacket[i])

                if not SkipUpdate:
                    if not ReturnString:
                        if not self.UpdateRegisterList(Register, RegisterValue, IsFile = True):
                            self.ComSyncError += 1
                            return "Error"
                    else:
                        if not self.UpdateRegisterList(Register, RegisterStringValue, IsString = True, IsFile = True):
                            self.ComSyncError += 1
                            return "Error"

            if ReturnString:
                return str(RegisterStringValue)
            return str(RegisterValue)
        except Exception as e1:
            self.LogErrorLine("Error in UpdateRegistersFromPacket: " + str(e1))
            return "Error"

     #------------ModbusProtocol::CheckCrc--------------------------------------
    def CheckCRC(self, Packet):

        try:
            if self.ModbusTCP:
                return True

            if len(Packet) == 0:
                return False
            ByteArray = bytearray(Packet[:len(Packet)-2])

            if sys.version_info[0] < 3:
                results = self.ModbusCrc(str(ByteArray))
            else:   # PYTHON3
                results = self.ModbusCrc(ByteArray)

            CRCValue = ( ( Packet[-1] & 0xFF ) << 8 ) | ( Packet[ -2] & 0xFF )
            if results != CRCValue:
                self.LogError("Data Error: CRC check failed: %04x  %04x" % (results, CRCValue))
                return False
            return True
        except Exception as e1:
            self.LogErrorLine("Error in CheckCRC: " + str(e1))
            self.LogHexList(Packet, prefix = "Packet")
            return False

     #------------ModbusProtocol::GetCRC----------------------------------------
    def GetCRC(self, Packet):
        try:
            if len(Packet) == 0:
                return None
            ByteArray = bytearray(Packet)

            if sys.version_info[0] < 3:
                results = self.ModbusCrc(str(ByteArray))
            else:   # PYTHON3
                results = self.ModbusCrc(ByteArray)

            return results
        except Exception as e1:
            self.LogErrorLine("Error in GetCRC: " + str(e1))
            self.LogHexList(Packet, prefix = "Packet")
            return None
    # ---------- ModbusProtocol::GetCommStats-----------------------------------
    def GetCommStats(self):
        SerialStats = []

        try:
            SerialStats.append({"Packet Count" : "M: %d, S: %d" % (self.TxPacketCount, self.RxPacketCount)})

            if self.CrcError == 0 or self.TxPacketCount == 0:
                PercentErrors = 0.0
            else:
                PercentErrors = float(self.CrcError) / float(self.TxPacketCount)

            if self.ComTimoutError == 0 or self.TxPacketCount == 0:
                PercentTimeoutErrors = 0.0
            else:
                PercentTimeoutErrors = float(self.ComTimoutError) / float(self.TxPacketCount)

            SerialStats.append({"CRC Errors" : "%d " % self.CrcError})
            SerialStats.append({"CRC Percent Errors" : ("%.2f" % (PercentErrors * 100)) + "%"})
            SerialStats.append({"Timeout Errors" : "%d" %  self.ComTimoutError})
            SerialStats.append({"Timeout Percent Errors" : ("%.2f" % (PercentTimeoutErrors * 100)) + "%"})
            SerialStats.append({"Modbus Exceptions" : self.ModbusException})
            SerialStats.append({"Validation Errors" : self.ComValidationError})
            SerialStats.append({"Sync Errors" : self.ComSyncError})
            SerialStats.append({"Invalid Data" : self.UnexpectedData})
            # add serial stats
            SerialStats.append({"Discarded Bytes" : "%d" % self.Slave.DiscardedBytes})
            SerialStats.append({"Comm Restarts" : "%d" % self.Slave.Restarts})

            CurrentTime = datetime.datetime.now()
            #
            Delta = CurrentTime - self.ModbusStartTime        # yields a timedelta object
            PacketsPerSecond = float((self.TxPacketCount + self.RxPacketCount)) / float(Delta.total_seconds())
            SerialStats.append({"Packets Per Second" : "%.2f" % (PacketsPerSecond)})

            if self.RxPacketCount:
                AvgTransactionTime = float(self.TotalElapsedPacketeTime / self.RxPacketCount)
                SerialStats.append({"Average Transaction Time" : "%.4f sec" % (AvgTransactionTime)})
        except Exception as e1:
            self.LogErrorLine("Error in GetCommStats: " + str(e1))
        return SerialStats

    # ---------- ModbusProtocol::ResetCommStats---------------------------------
    def ResetCommStats(self):

        try:
            self.RxPacketCount = 0
            self.TxPacketCount = 0
            self.TotalElapsedPacketeTime = 0
            self.ModbusStartTime = datetime.datetime.now()     # used for com metrics
            self.Slave.ResetSerialStats()
        except Exception as e1:
            self.LogErrorLine("Error in ResetCommStats: " + str(e1))

    #------------ModbusProtocol::Flush------------------------------------------
    def Flush(self):

        self.Slave.Flush()

    #------------ModbusProtocol::Close------------------------------------------
    def Close(self):
        self.IsStopping = True
        self.Slave.Close()
Exemplo n.º 3
0
class ModbusProtocol(ModbusBase):
    def __init__(self,
        updatecallback,
        address = 0x9d,
        name = "/dev/serial0",
        rate=9600,
        Parity = None,
        OnePointFiveStopBits = None,
        config = None):

        super(ModbusProtocol, self).__init__(updatecallback = updatecallback, address = address, name = name, rate = rate, config = config)

        try:

            # ~3000 for 9600               bit time * 10 bits * 10 char * 2 packets + wait time(3000) (convert to ms * 1000)
            self.ModBusPacketTimoutMS = (((((1/float(rate)) * 10.0) * 10.0 * 2.0) *1000.0)  + 3000.0)     # .00208

            self.ModBusPacketTimoutMS += self.AdditionalModbusTimeout * 1000.0


            if self.UseTCP:
                self.ModBusPacketTimoutMS = self.ModBusPacketTimoutMS
            #Starting serial connection
            if self.UseTCP:
                self.Slave = SerialTCPDevice(config = self.config)
            else:
                self.Slave = SerialDevice(name = name, rate = rate, Parity = Parity, OnePointFiveStopBits = OnePointFiveStopBits, config = self.config)
            self.Threads = self.MergeDicts(self.Threads, self.Slave.Threads)


        except Exception as e1:
            self.LogErrorLine("Error opening serial device: " + str(e1))
            self.FatalError("Error opening serial device.")

        try:
            # CRCMOD library, used for CRC calculations
            self.ModbusCrc = crcmod.predefined.mkCrcFun('modbus')
            self.InitComplete = True
        except Exception as e1:
            self.FatalError("Unable to find crcmod package: " + str(e1))

    #--------------------ModbusProtocol:GetExceptionString----------------------
    def GetExceptionString(self, Code):

        try:

            LookUp = {
                MBUS_EXCEP_FUNCTION : "Illegal Function",
                MBUS_EXCEP_ADDRESS : "Illegal Address",
                MBUS_EXCEP_DATA : "Illegal Data Value",
                MBUS_EXCEP_SLAVE_FAIL : "Slave Device Failure",
                MBUS_EXCEP_ACK : "Acknowledge",
                MBUS_EXCEP_BUSY : "Slave Device Busy",
                MBUS_EXCEP_NACK : "Negative Acknowledge",
                MBUS_EXCEP_MEM_PE : "Memory Parity Error",
                MBUS_EXCEP_GATEWAY : "Gateway Path Unavailable",
                MBUS_EXCEP_GATEWAY_TG : "Gateway Target Device Failed to Respond"
             }

            ReturnString = LookUp.get(Code, "Unknown")
            ReturnString = ReturnString + (": %02x" % Code)
            return ReturnString
        except Exception as e1:
            self.LogErrorLine("Error in GetExceptionString: " + str(e1))

        return ""
    # ---------- ModbusProtocol::CheckResponseAddress---------------------------
    def CheckResponseAddress(self, Address):

        if Address == self.Address:
            return True
        if self.ResponseAddress == None:
            return False
        if Address == self.ResponseAddress:
            return True
        return False

    # ---------- ModbusProtocol::GetPacketFromSlave-----------------------------
    #  This function returns two values, the first is boolean. The seconds is
    #  a packet (list). If the return value is True and an empty packet, then
    #  keep looking because the data has not arrived yet, if return is False there
    #  is and error. If True and a non empty packet then it is valid data
    def GetPacketFromSlave(self):

        LocalErrorCount = 0
        Packet = []
        EmptyPacket = []    # empty packet
        try:
            if not len(self.Slave.Buffer):
                return True, EmptyPacket

            if not self.CheckResponseAddress(self.Slave.Buffer[MBUS_OFF_ADDRESS]):
                self.DiscardByte()
                self.Flush()
                return False, EmptyPacket

            if len(self.Slave.Buffer) < MIN_PACKET_ERR_LENGTH:
                return True, EmptyPacket   # No full packet ready

            if self.Slave.Buffer[MBUS_OFF_COMMAND] & MBUS_ERROR_BIT:
                for i in range(0, MIN_PACKET_ERR_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Excetion code, and CRC
                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    self.SlaveException += 1
                    self.LogError("Modbus Exception: " + self.GetExceptionString(Packet[MBUS_OFF_EXCEPTION]) + " , Modbus Command: " + ("%02x" % Packet[MBUS_OFF_COMMAND]))
                else:
                    self.CrcError += 1
                return False, Packet

            if len(self.Slave.Buffer) < MIN_PACKET_RESPONSE_LENGTH:
                return True, EmptyPacket   # No full packet ready

            if  self.Slave.Buffer[MBUS_OFF_COMMAND] in [MBUS_CMD_READ_REGS]:
                # it must be a read command response
                length = self.Slave.Buffer[MBUS_OFF_RESPONSE_LEN]   # our packet tells us the length of the payload
                # if the full length of the packet has not arrived, return and try again
                if (length + MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH) > len(self.Slave.Buffer):
                    return True, EmptyPacket

                for i in range(0, length + MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Length, message and CRC

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packet
            elif self.Slave.Buffer[MBUS_OFF_COMMAND] in [MBUS_CMD_WRITE_REGS]:
                # it must be a write command response
                if len(self.Slave.Buffer) < MIN_PACKET_MIN_WRITE_RESPONSE_LENGTH:
                    return True, EmptyPacket
                for i in range(0, MIN_PACKET_MIN_WRITE_RESPONSE_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))    # address, function, address hi, address low, quantity hi, quantity low, CRC high, crc low

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packet
            elif self.Slave.Buffer[MBUS_OFF_COMMAND] in [MBUS_CMD_READ_FILE]:
                length = self.Slave.Buffer[MBUS_OFF_RESPONSE_LEN]   # our packet tells us the length of the payload
                if self.Slave.Buffer[MBUS_OFF_FILE_TYPE] != MBUS_FILE_TYPE_VALUE:
                    self.LogError("Invalid modbus file record type")
                    self.ComValidationError += 1
                    return False, EmptyPacket
                # if the full length of the packet has not arrived, return and try again
                if (length + MBUS_FILE_READ_PAYLOAD_SIZE_MINUS_LENGTH) > len(self.Slave.Buffer):
                    return True, EmptyPacket
                for i in range(0, length + MBUS_FILE_READ_PAYLOAD_SIZE_MINUS_LENGTH):
                    Packet.append(self.Slave.Buffer.pop(0))  # pop Address, Function, Length, message and CRC

                if self.CheckCRC(Packet):
                    self.RxPacketCount += 1
                    return True, Packet
                else:
                    self.CrcError += 1
                    return False, Packet
            else:
                # received a  response to a command we do not support
                self.DiscardByte()
                self.Flush()
                return False, EmptyPacket
        except Exception as e1:
            self.LogErrorLine("Error in GetPacketFromSlave: " + str(e1))
            self.SlaveException += 1
            return False, EmptyPacket
    # ---------- GeneratorDevice::DiscardByte-----------------------------------
    def DiscardByte(self):

        discard = self.Slave.DiscardByte()
        self.LogError("Discarding byte slave: %02x" % (discard))

    #-------------ModbusProtocol::ProcessMasterSlaveWriteTransaction------------
    def ProcessMasterSlaveWriteTransaction(self, Register, Length, Data):

        try:
            MasterPacket = []

            MasterPacket = self.CreateMasterPacket(Register, length = int(Length), command = MBUS_CMD_WRITE_REGS, data = Data)

            if len(MasterPacket) == 0:
                return False

            return self.ProcessOneTransaction(MasterPacket, skipupdate = True)   # True to skip writing results to cached reg values
        except Exception as e1:
            self.LogErrorLine("Error in ProcessMasterSlaveWriteTransaction: " + str(e1))
            return False

    #-------------ModbusProtocol::ProcessMasterSlaveTransaction-----------------
    def ProcessMasterSlaveTransaction(self, Register, Length, skipupdate = False, ReturnString = False):

        MasterPacket = []

        try:
            MasterPacket = self.CreateMasterPacket(Register, length = int(Length))

            if len(MasterPacket) == 0:
                return ""

            return self.ProcessOneTransaction(MasterPacket, skipupdate = skipupdate, ReturnString = ReturnString)     # don't log

        except Exception as e1:
            self.LogErrorLine("Error in ProcessMasterSlaveTransaction: " + str(e1))
            return ""

    #-------------ModbusProtocol::ProcessMasterSlaveFileReadTransaction---------
    def ProcessMasterSlaveFileReadTransaction(self, Register, Length, skipupdate = False, file_num = 1, ReturnString = False):

        MasterPacket = []

        try:
            MasterPacket = self.CreateMasterPacket(Register, length = int(Length), command = MBUS_CMD_READ_FILE, file_num = file_num)

            if len(MasterPacket) == 0:
                return ""

            return self.ProcessOneTransaction(MasterPacket, skipupdate = skipupdate, ReturnString = ReturnString)     # don't log

        except Exception as e1:
            self.LogErrorLine("Error in ProcessMasterSlaveFileReadTransaction: " + str(e1))
            return ""

    #------------ModbusProtocol::ProcessOneTransaction--------------------------
    def ProcessOneTransaction(self, MasterPacket, skipupdate = False, ReturnString = False):

        try:
            with self.CommAccessLock:       # this lock should allow calls from multiple threads

                if len(self.Slave.Buffer):
                    self.UnexpectedData += 1
                    self.LogError("Flushing, unexpected data. Likely timeout.")
                    self.Flush()
                self.SendPacketAsMaster(MasterPacket)

                SentTime = datetime.datetime.now()
                while True:
                    # be kind to other processes, we know we are going to have to wait for the packet to arrive
                    # so let's sleep for a bit before we start polling
                    if self.SlowCPUOptimization:
                        time.sleep(0.03)
                    else:
                        time.sleep(0.01)

                    if self.IsStopping:
                        return ""
                    RetVal, SlavePacket = self.GetPacketFromSlave()

                    if RetVal == True and len(SlavePacket) != 0:    # we receive a packet
                        self.TotalElapsedPacketeTime += (self.MillisecondsElapsed(SentTime) / 1000)
                        break
                    if RetVal == False:
                        self.LogError("Error Receiving slave packet for register %04x" % (self.GetRegisterFromPacket(MasterPacket) ))
                        # Errors returned here are logged in GetPacketFromSlave
                        self.Flush()
                        return ""

                    msElapsed = self.MillisecondsElapsed(SentTime)
                    # This normally takes about 30 ms however in some instances it can take up to 950ms
                    # the theory is this is either a delay due to how python does threading, or
                    # delay caused by the generator controller.
                    # each char time is about 1 millisecond (at 9600 baud) so assuming a 10 byte packet
                    # transmitted and a 10 byte received with about 5 char times of silence
                    # in between should give us about 25ms
                    if msElapsed > self.ModBusPacketTimoutMS:
                        self.ComTimoutError += 1
                        self.LogError("Error: timeout receiving slave packet for register %04x Buffer:%d" % (self.GetRegisterFromPacket(MasterPacket), len(self.Slave.Buffer)) )
                        if len(self.Slave.Buffer):
                            self.LogError("Buffer: " + str(self.Slave.Buffer))
                        self.Flush()
                        return ""

                # update our cached register dict
                ReturnRegValue = self.UpdateRegistersFromPacket(MasterPacket, SlavePacket, SkipUpdate = skipupdate, ReturnString = ReturnString)
                if ReturnRegValue == "Error":
                    self.LogError("Master: " + str(MasterPacket))
                    self.LogError("Slave: " + str(SlavePacket))
                    self.ComValidationError += 1
                    self.Flush()
                    ReturnRegValue = ""

            return ReturnRegValue


        except Exception as e1:
            self.LogErrorLine("Error in ProcessOneTransaction: " + str(e1))
            return ""

    # ---------- ModbusProtocol::MillisecondsElapsed----------------------------
    def MillisecondsElapsed(self, ReferenceTime):

        CurrentTime = datetime.datetime.now()
        Delta = CurrentTime - ReferenceTime
        return Delta.total_seconds() * 1000

    #------------GetRegisterFromPacket -----------------------------------------
    def GetRegisterFromPacket(self, Packet):
        try:
            Register = 0
            if Packet[MBUS_OFF_COMMAND] == MBUS_CMD_READ_FILE:
                Register = Packet[MBUS_OFF_FILE_RECORD_HI] << 8 |  Packet[MBUS_OFF_FILE_RECORD_LOW] & 0x00FF
            else:
                Register = Packet[MBUS_OFF_REGISTER_HI] << 8 |  Packet[MBUS_OFF_REGISTER_LOW] & 0x00FF
            return Register
        except Exception as e1:
            self.LogErrorLine("Error in GetRegisterFromPacket: " + str(e1))
            return Register

    # ---------- ModbusProtocol::CreateMasterPacket-----------------------------
    # the length is the register length in words, as required by modbus
    # build Packet
    def CreateMasterPacket(self, register, length = 1, command = MBUS_CMD_READ_REGS, data=[], file_num = 1):

        Packet = []
        try:
            RegisterInt = int(register,16)

            if RegisterInt < MIN_REGISTER or RegisterInt > MAX_REGISTER:
                self.ComValidationError += 1
                self.LogError("Validation Error: CreateMasterPacket maximum regiseter value exceeded: " + str(register))
                return []
            if file_num < MIN_FILE_NUMBER or file_num > MAX_FILE_NUMBER:
                self.ComValidationError += 1
                self.LogError("Validation Error: CreateMasterPacket maximum file number value exceeded: " + str(file_num))
                return []

            if command == MBUS_CMD_READ_REGS:
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(RegisterInt >> 8)             # reg higy
                Packet.append(RegisterInt & 0x00FF)         # reg low
                Packet.append(length >> 8)                  # length high
                Packet.append(length & 0x00FF)              # length low
                CRCValue = self.GetCRC(Packet)
                Packet.append(CRCValue & 0x00FF)            # CRC low
                Packet.append(CRCValue >> 8)                # CRC high

            elif command == MBUS_CMD_WRITE_REGS:
                if len(data) == 0:
                    self.LogError("Validation Error: CreateMasterPacket invalid length (1) %x %x" % (len(data), length))
                    self.ComValidationError += 1
                    return []
                if len(data)/2 != length:
                    self.LogError("Validation Error: CreateMasterPacket invalid length (2) %x %x" % (len(data), length))
                    self.ComValidationError += 1
                    return []
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(RegisterInt >> 8)             # reg higy
                Packet.append(RegisterInt & 0x00FF)         # reg low
                Packet.append(length >> 8)                  # Num of Reg higy
                Packet.append(length & 0x00FF)              # Num of Reg low
                Packet.append(len(data))                    # byte count
                for b in range(0, len(data)):
                    Packet.append(data[b])                  # data
                CRCValue = self.GetCRC(Packet)
                Packet.append(CRCValue & 0x00FF)            # CRC low
                Packet.append(CRCValue >> 8)                # CRC high

            elif command == MBUS_CMD_READ_FILE:

                # Note, we only support one sub request at at time
                if RegisterInt < MIN_FILE_RECORD_NUM or RegisterInt > MAX_FILE_RECORD_NUM:
                    self.ComValidationError += 1
                    self.LogError("Validation Error: CreateMasterPacket maximum regiseter (record number) value exceeded: " + str(register))
                    return []
                Packet.append(self.Address)                 # address
                Packet.append(command)                      # command
                Packet.append(MBUS_READ_FILE_REQUEST_PAYLOAD_LENGTH)     # Byte count
                Packet.append(MBUS_FILE_TYPE_VALUE)         # always same value
                Packet.append(file_num >> 8)                # File Number hi
                Packet.append(file_num & 0x00FF)            # File Number low
                Packet.append(RegisterInt >> 8)             # register (file record number) high
                Packet.append(RegisterInt & 0x00FF)         # register (file record number) low
                Packet.append(length >> 8)                  # Length to return hi
                Packet.append(length & 0x00FF)              # Length to return lo
                CRCValue = self.GetCRC(Packet)
                Packet.append(CRCValue & 0x00FF)            # CRC low
                Packet.append(CRCValue >> 8)                # CRC high
            else:
                self.LogError("Validation Error: Invalid command in CreateMasterPacket!")
                self.ComValidationError += 1
                return []
        except Exception as e1:
            self.LogErrorLine("Error in CreateMasterPacket: " + str(e1))

        if len(Packet) > MAX_MODBUS_PACKET_SIZE:
            self.LogError("Validation Error: CreateMasterPacket: Packet size exceeds max size")
            self.ComValidationError += 1
            return []

        return Packet

    #-------------ModbusProtocol::SendPacketAsMaster----------------------------
    def SendPacketAsMaster(self, Packet):

        try:
            ByteArray = bytearray(Packet)
            self.Slave.Write(ByteArray)
            self.TxPacketCount += 1
        except Exception as e1:
            self.LogErrorLine("Error in SendPacketAsMaster: " + str(e1))
            self.LogError("Packet: " + str(Packet))

    # ---------- ModbusProtocol::UpdateRegistersFromPacket----------------------
    #    Update our internal register list based on the request/response packet
    def UpdateRegistersFromPacket(self, MasterPacket, SlavePacket, SkipUpdate = False, ReturnString = False):

        try:
            if len(MasterPacket) < MIN_PACKET_RESPONSE_LENGTH or len(SlavePacket) < MIN_PACKET_RESPONSE_LENGTH:
                self.LogError("Validation Error, length: Master" + str(len(MasterPacket)) + " Slave: " + str(len(SlavePacket)))
                return "Error"

            if MasterPacket[MBUS_OFF_ADDRESS] != self.Address:
                self.LogError("Validation Error: Invalid address in UpdateRegistersFromPacket (Master)")
                return "Error"
            if not self.CheckResponseAddress(SlavePacket[MBUS_OFF_ADDRESS]):
                self.LogError("Validation Error: Invalid address in UpdateRegistersFromPacket (Slave)")
                return "Error"
            if not SlavePacket[MBUS_OFF_COMMAND] in [MBUS_CMD_READ_REGS, MBUS_CMD_WRITE_REGS, MBUS_CMD_READ_FILE]:
                self.LogError("Validation Error: Unknown Function slave %02x %02x" %  (SlavePacket[MBUS_OFF_ADDRESS],SlavePacket[MBUS_OFF_COMMAND]))
                return "Error"
            if not MasterPacket[MBUS_OFF_COMMAND] in [MBUS_CMD_READ_REGS, MBUS_CMD_WRITE_REGS, MBUS_CMD_READ_FILE]:
                self.LogError("Validation Error: Unknown Function master %02x %02x" %  (MasterPacket[MBUS_OFF_ADDRESS],MasterPacket[MBUS_OFF_COMMAND]))
                return "Error"

            if MasterPacket[MBUS_OFF_COMMAND] != SlavePacket[MBUS_OFF_COMMAND]:
                self.LogError("Validation Error: Command Mismatch :" + str(MasterPacket[MBUS_OFF_COMMAND]) + ":" + str(SlavePacket[MBUS_OFF_COMMAND]))
                return "Error"

             # get register from master packet
            Register = "%04x" % (self.GetRegisterFromPacket(MasterPacket))
            if MasterPacket[MBUS_OFF_COMMAND] == MBUS_CMD_WRITE_REGS:
                # get register from slave packet
                SlaveRegister = "%04x" % (self.GetRegisterFromPacket(SlavePacket))
                if SlaveRegister != Register:
                    self.LogError("Validation Error: Master Slave Register Mismatch : " + Register +  ":" + SlaveRegister)
                    return "Error"

            RegisterValue = ""
            RegisterStringValue = ""
            if MasterPacket[MBUS_OFF_COMMAND] == MBUS_CMD_READ_REGS:
                # get value from slave packet
                length = SlavePacket[MBUS_OFF_RESPONSE_LEN]
                if (length + MBUS_RES_PAYLOAD_SIZE_MINUS_LENGTH) > len(SlavePacket):
                    self.LogError("Validation Error: Slave Lenght : " + length +  ":" + len(SlavePacket))
                    return "Error"

                for i in range(3, length+3):
                    RegisterValue += "%02x" % SlavePacket[i]
                    if ReturnString:
                        if SlavePacket[i]:
                            RegisterStringValue += chr(SlavePacket[i])
                # update register list
                if not SkipUpdate:
                    if not self.UpdateRegisterList == None:
                        if not ReturnString:
                            self.UpdateRegisterList(Register, RegisterValue)
                        else:
                            self.UpdateRegisterList(Register, RegisterStringValue, IsString = True)

            if MasterPacket[MBUS_OFF_COMMAND] == MBUS_CMD_READ_FILE:
                payloadLen = SlavePacket[MBUS_OFF_FILE_PAYLOAD_LEN]
                payloadLen -= 1
                for i  in range (MBUS_OFF_FILE_PAYLOAD, MBUS_OFF_FILE_PAYLOAD + payloadLen):
                    RegisterValue += "%02x" % SlavePacket[i]
                    if ReturnString:
                        if SlavePacket[i]:
                            RegisterStringValue += chr(SlavePacket[i])

                if not SkipUpdate:
                    if not ReturnString:
                        self.UpdateRegisterList(Register, RegisterValue, IsFile = True)
                    else:
                        self.UpdateRegisterList(Register, RegisterStringValue, IsString = True, IsFile = True)
                pass
            if ReturnString:
                return str(RegisterStringValue)
            return str(RegisterValue)
        except Exception as e1:
            self.LogErrorLine("Error in UpdateRegistersFromPacket: " + str(e1))
            return "Error"

     #------------ModbusProtocol::CheckCrc--------------------------------------
    def CheckCRC(self, Packet):

        try:
            if len(Packet) == 0:
                return False
            ByteArray = bytearray(Packet[:len(Packet)-2])

            if sys.version_info[0] < 3:
                results = self.ModbusCrc(str(ByteArray))
            else:   # PYTHON3
                results = self.ModbusCrc(ByteArray)

            CRCValue = ( ( Packet[-1] & 0xFF ) << 8 ) | ( Packet[ -2] & 0xFF )
            if results != CRCValue:
                self.LogError("Data Error: CRC check failed: %04x  %04x" % (results, CRCValue))
                return False
            return True
        except Exception as e1:
            self.LogErrorLine("Error in CheckCRC: " + str(e1))
            self.LogError("Packet: " + str(Packet))
            return False

     #------------ModbusProtocol::GetCRC----------------------------------------
    def GetCRC(self, Packet):
        try:
            if len(Packet) == 0:
                return False
            ByteArray = bytearray(Packet)

            if sys.version_info[0] < 3:
                results = self.ModbusCrc(str(ByteArray))
            else:   # PYTHON3
                results = self.ModbusCrc(ByteArray)

            return results
        except Exception as e1:
            self.LogErrorLine("Error in GetCRC: " + str(e1))
            self.LogError("Packet: " + str(Packet))
            return 0
    # ---------- ModbusProtocol::GetCommStats-----------------------------------
    def GetCommStats(self):
        SerialStats = []

        try:
            SerialStats.append({"Packet Count" : "M: %d, S: %d" % (self.TxPacketCount, self.RxPacketCount)})

            if self.CrcError == 0 or self.TxPacketCount == 0:
                PercentErrors = 0.0
            else:
                PercentErrors = float(self.CrcError) / float(self.TxPacketCount)

            if self.ComTimoutError == 0 or self.TxPacketCount == 0:
                PercentTimeoutErrors = 0.0
            else:
                PercentTimeoutErrors = float(self.ComTimoutError) / float(self.TxPacketCount)

            SerialStats.append({"CRC Errors" : "%d " % self.CrcError})
            SerialStats.append({"CRC Percent Errors" : ("%.2f" % (PercentErrors * 100)) + "%"})
            SerialStats.append({"Packet Timeouts" : "%d" %  self.ComTimoutError})
            SerialStats.append({"Packet Timeouts Percent Errors" : ("%.2f" % (PercentTimeoutErrors * 100)) + "%"})
            SerialStats.append({"Modbus Exceptions" : self.SlaveException})
            SerialStats.append({"Validation Errors" : self.ComValidationError})
            SerialStats.append({"Invalid Data" : self.UnexpectedData})
            # add serial stats
            SerialStats.append({"Discarded Bytes" : "%d" % self.Slave.DiscardedBytes})
            SerialStats.append({"Comm Restarts" : "%d" % self.Slave.Restarts})

            CurrentTime = datetime.datetime.now()
            #
            Delta = CurrentTime - self.ModbusStartTime        # yields a timedelta object
            PacketsPerSecond = float((self.TxPacketCount + self.RxPacketCount)) / float(Delta.total_seconds())
            SerialStats.append({"Packets Per Second" : "%.2f" % (PacketsPerSecond)})

            if self.RxPacketCount:
                AvgTransactionTime = float(self.TotalElapsedPacketeTime / self.RxPacketCount)
                SerialStats.append({"Average Transaction Time" : "%.4f sec" % (AvgTransactionTime)})
        except Exception as e1:
            self.LogErrorLine("Error in GetCommStats: " + str(e1))
        return SerialStats

    # ---------- ModbusProtocol::ResetCommStats---------------------------------
    def ResetCommStats(self):

        try:
            self.RxPacketCount = 0
            self.TxPacketCount = 0
            self.TotalElapsedPacketeTime = 0
            self.ModbusStartTime = datetime.datetime.now()     # used for com metrics
            self.Slave.ResetSerialStats()
        except Exception as e1:
            self.LogErrorLine("Error in ResetCommStats: " + str(e1))

    #------------ModbusProtocol::Flush------------------------------------------
    def Flush(self):

        self.Slave.Flush()

    #------------ModbusProtocol::Close------------------------------------------
    def Close(self):
        self.IsStopping = True
        self.Slave.Close()