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))
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()
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()