def __init__(self, adapter, port): """ :param adapter: The adapter that will serve as an interface for interacting with the broker """ self._port = port # A list of items that we will need to discover for. # The base protocol will use this dictionary to feed items to # the UI self.items = [] self._error_codes = [] # The list of connected item IDs found in the initial sweep in # connectionMade() self._item_ids = [] BaseProtocol.__init__(self) # Set the LineReceiver to line mode. This causes lineReceived to be called # when data is sent to the serial port. We will get a line whenever the END_BYTE # appears in the buffer self.setLineMode() self.delimiter = END_BYTE_STR # The buffer that we will be storing the data that arrives via the serial connection self._binary_buffer = bytearray() self.adapter = adapter # Event IDs are 16-bit (2 byte) numbers so we need a radix # of 65535 or 0xFFFF in hex # NOTE: The number of bits in an event ID is subject to change, # the constant NUM_EVENT_ID_BITS can easily be changed to accommodate this. self._event_id_generator = message_id_generator((2**self.NUM_EVENT_ID_BITS)) # From parlay.utils, calls _message_queue_handler() whenever # a new message is added to the MessageQueue object self._message_queue = MessageQueue(self._message_queue_handler) self._attached_item_d = None # Dictionary that maps ID # to Deferred object self._discovery_msg_ids = {} # Sequence number is a nibble as of now, so the domain should be # 0 <= seq number <= 15 # which means the radix will be 16, but to be safe I'll do # 2^SEQ_BITS where SEQ_BITS is a member constant that can easily be changed self._seq_num = message_id_generator((2**self.SEQ_BITS)) # ACKs should be deferred objects because you want to catch them on the way # back via asynchronous communication. self._ack_deferred = defer.Deferred() # Store discovered item IDs so that we do not push duplicates to the # item requesting the discovery self._in_progress = False self._discovery_deferred = defer.Deferred() self._ack_table = {seq_num : defer.Deferred() for seq_num in range(2**self.SEQ_BITS)} self._ack_window = SlidingACKWindow(self.WINDOW_SIZE, self.NUM_RETRIES)
class PCOMSerial(BaseProtocol, LineReceiver): # Constant number of retries before another message is sent out # after not receiving an ACK NUM_RETRIES = 3 # The item ID of the protocol during discovery. DISCOVERY_CODE = 0xfefe # The minimum event ID. Some event IDs may need to be reserved # in the future. MIN_EVENT_ID = 0 # The number of bits reserved for the event ID in the serialized # event protocol. Eg. if two bytes are reserved this number should be 16. NUM_EVENT_ID_BITS = 16 # Item ID of the embedded reactor EMBD_REACTOR_ID = 0 BROADCAST_SUBSYSTEM_ID = 0x8000 SUBSYSTEM_SHIFT = 8 # Number of bits we have for sequence number SEQ_BITS = 4 # baud rate of communication over serial line BAUD_RATE = 115200 # ACK window size WINDOW_SIZE = 8 ACK_DIFFERENTIAL = 8 # timeout before resend in secs ACK_TIMEOUT = .5 @classmethod def open(cls, adapter, port): """ :param cls: The class object :param adapter: current adapter instance used to interface with broker :param port: the serial port device to use. :param baudrate: the baudrate that will be set by user. :return: returns the instantiated protocol object '""" # Make sure port is not a list port = port[0] if isinstance(port, list) else port protocol = PCOMSerial(adapter, port) SerialPort(protocol, port, adapter.reactor, baudrate=cls.BAUD_RATE) return protocol @classmethod def get_open_params_defaults(cls): """ Returns a list of parameters defaults. These will be displayed in the UI. :return: default args: the default arguments provided to the user in the UI """ default_args = BaseProtocol.get_open_params_defaults() potential_serials = [port_list[0] for port_list in list_ports.comports()] default_args['port'] = potential_serials return default_args def close(self): """ Simply close the connection :return: """ self.transport.loseConnection() return defer.succeed(None) def __str__(self): return "PCOM @ " + str(self._port) def __init__(self, adapter, port): """ :param adapter: The adapter that will serve as an interface for interacting with the broker """ self._port = port # A list of items that we will need to discover for. # The base protocol will use this dictionary to feed items to # the UI self.items = [] self._error_codes = [] # The list of connected item IDs found in the initial sweep in # connectionMade() self._item_ids = [] BaseProtocol.__init__(self) # Set the LineReceiver to line mode. This causes lineReceived to be called # when data is sent to the serial port. We will get a line whenever the END_BYTE # appears in the buffer self.setLineMode() self.delimiter = END_BYTE_STR # The buffer that we will be storing the data that arrives via the serial connection self._binary_buffer = bytearray() self.adapter = adapter # Event IDs are 16-bit (2 byte) numbers so we need a radix # of 65535 or 0xFFFF in hex # NOTE: The number of bits in an event ID is subject to change, # the constant NUM_EVENT_ID_BITS can easily be changed to accommodate this. self._event_id_generator = message_id_generator((2**self.NUM_EVENT_ID_BITS)) # From parlay.utils, calls _message_queue_handler() whenever # a new message is added to the MessageQueue object self._message_queue = MessageQueue(self._message_queue_handler) self._attached_item_d = None # Dictionary that maps ID # to Deferred object self._discovery_msg_ids = {} # Sequence number is a nibble as of now, so the domain should be # 0 <= seq number <= 15 # which means the radix will be 16, but to be safe I'll do # 2^SEQ_BITS where SEQ_BITS is a member constant that can easily be changed self._seq_num = message_id_generator((2**self.SEQ_BITS)) # ACKs should be deferred objects because you want to catch them on the way # back via asynchronous communication. self._ack_deferred = defer.Deferred() # Store discovered item IDs so that we do not push duplicates to the # item requesting the discovery self._in_progress = False self._discovery_deferred = defer.Deferred() self._ack_table = {seq_num : defer.Deferred() for seq_num in range(2**self.SEQ_BITS)} self._ack_window = SlidingACKWindow(self.WINDOW_SIZE, self.NUM_RETRIES) def send_error_message(self, original_message, message_status): """ Sends a notification error to the destination ID. :param original_message: PCOM Message object that holds the IDs of the sender and receiver :param message_status: Message status code that translates to an error message. :return: """ error_msg = pcom_message.PCOMMessage(to=original_message.from_, from_=original_message.to, msg_status=message_status, msg_id=original_message.msg_id) json_msg = error_msg.to_json_msg() self.adapter.publish(json_msg) def _message_queue_handler(self, message): """ This is the callback function given to the MessageQueue object that is called whenever a new message is added to the queue. This function does the actual writing to the serial port. :type message dict :param message: dictionary message received from Parlay """ # this function should return a fired deferred, so set one up d = defer.Deferred() d.callback(None) # print "MESSAGE", message s = pcom_message.PCOMMessage.from_json_msg(message) # Serialize the message and prepare for protocol wrapping. try: packet = encode_pcom_message(s) except: self.send_error_message(original_message=s, message_status="Unable to encode message: {0}".format(s)) return d need_ack = True # Get the next sequence number and then wrap the protocol with # the desired low level byte wrapping and send down serial line sequence_num = self._seq_num.next() packet = str(wrap_packet(packet, sequence_num, need_ack)) # print "SENT MESSAGE: ", [hex(ord(x)) for x in packet] # Write to serial line! Good luck packet. self._ack_window.add(ACKInfo(sequence_num, 0, packet, self.transport)) return d def _discovery_listener(self, msg): """ We need did this function to fire the deferred objects based on the msg we receive. If the message ID matches an ID in the dictionary, fire the deferred. :type msg PCOMMessage """ # Return if there aren't any IDs left if len(self._discovery_msg_ids) == 0: return if msg.category() == MessageCategory.Order_Response and msg.msg_id in self._discovery_msg_ids: # If the message was a response and matched an ID in the dictionary, remove it and fire the # corresponding Deferred object. self._discovery_msg_ids.pop(msg.msg_id).callback(msg) return """ The following functions aid in the discovery protocol. They may be condensed into fewer functions that require more parameters, but I thought abstracting each message would making understanding the protocol easier. """ @defer.inlineCallbacks def get_property_name(self, to, requested_property_id): """ Sends a message down the serial line requesting the command name of a given command ID, used in discovery protocol :param to: destination item ID :param requested_property_id: property ID that we want to know the name of :return: name of the property from Embedded Core """ response = yield self.send_command(to, command_id=GET_PROPERTY_NAME, params=["property_id"], data=[requested_property_id]) # The data in the response message will be a list, # the property name should be in the 0th position # and strip the NULL byte. defer.returnValue(response.data[0]) @defer.inlineCallbacks def get_command_name(self, to, requested_command_id): """ Sends a messge down the serial line requesting the property name of a given property ID, used in discovery protocol :param to: destination ID :param requested_command_id: command ID that we want to know the name of :return: name from Embedded Core """ response = yield self.send_command(to, command_id=GET_COMMAND_NAME, params=["command_id"], data=[requested_command_id]) # The data in the response message will be a list, # the command name should be in the 0th position defer.returnValue(response.data[0]) @defer.inlineCallbacks def get_command_input_param_format(self, to, requested_command_id): """ Given a command ID and item ID, sends a message to the item ID requesting the format of its input parameters. This functions should return a string that describes each parameter. NOTE: variable arrays are indicated with a *. Eg. A list of ints would be "*i". See format string details for character->byte translation. :param to: destination item ID :param requested_command_id: command ID that we want the parameter format of :return: format string describing input parameters """ response = yield self.send_command(to, command_id=GET_COMMAND_INPUT_PARAM_FORMAT, params=["command_id"], data=[requested_command_id]) r_val = '' if len(response.data) == 0 else response.data[0] defer.returnValue(r_val) @defer.inlineCallbacks def get_command_input_param_names(self, to, requested_command_id): """ Given an item ID and a command ID, requests the parameter names of the command from the item. Returns a list of names (comma delimited) that represent the parameter names. TODO: change return value to string? Eg. "frequency,duty cycle" :param to: destination item ID :param requested_command_id: command id to find the parameter names of :return: a list of parameter names """ response = yield self.send_command(to, command_id=GET_COMMAND_INPUT_PARAM_NAMES, params=["command_id"], data=[requested_command_id]) param_names = [] if len(response.data) == 0 else [x.strip() for x in response.data[0].split(',')] defer.returnValue(param_names) @defer.inlineCallbacks def get_command_output_parameter_desc(self, to, requested_command_id): """ Given an item ID and a command ID, requests the output description Returns a list of names (comma delimited) that represent the output names TODO: change return value to string? Eg. "frequency,duty cycle" :param to: destination item ID :param requested_command_id: command id to find the parameter names of :return: a list of parameter names """ response = yield self.send_command(to, command_id=GET_COMMAND_OUTPUT_PARAM_DESC, params=["command_id"], data=[requested_command_id]) list_of_names = [] if len(response.data) == 0 else [x.strip() for x in response.data[0].split(',')] defer.returnValue(list_of_names) @defer.inlineCallbacks def get_property_type(self, to, requested_property_id): """ Given a property ID, requests the property's type from the item ID. Gets back a format string. :param to: destination item ID :param requested_property_id: property ID that we want the type of :return: format string describing the type """ response = yield self.send_command(to, command_id=GET_PROPERTY_TYPE, params=["property_id"], data=[requested_property_id]) r_val = '' if len(response.data) == 0 else response.data[0] defer.returnValue(r_val) def send_command(self, to, tx_type="DIRECT", command_id=0, msg_status="INFO", response_req=True, params=[], data=[]): """ Send a command and return a deferred that will succeed on a response and with the response :param to: destination item ID :param tx_type: DIRECT or BROADCAST :param command_id: ID of the command :param msg_status: status of the message: ERROR, WARNING, INFO, PROGRESS, or OK :param response_req: boolean whether or not a response is required :param params: command parameters :param data: data that corresponds to each parameter :return: """ # Increment the event ID event_id = self._event_id_generator.next() # Construct the message based on the parameters # For now "FROM:" will always be the discovery code, # this needs to change in the future. # Build the TOPICS portion topics = { "MSG_ID": event_id, "TX_TYPE": tx_type, "MSG_TYPE": "COMMAND", "RESPONSE_REQ": response_req, "MSG_STATUS": msg_status, # NOTE: Change this to handle sending and receiving across subsystems. "FROM": self.DISCOVERY_CODE, "TO": to } # Build the CONTENTS portion contents = { "COMMAND": command_id, } # If data was given via function arguments we need to pack it # into the contents portion of the message to resemble a JSON message. for parameter, data_val in zip(params, data): contents[parameter] = data_val # If we need to wait the result should be a deferred object. if response_req: result = defer.Deferred() # Add the correct mapping to the dictionary self._discovery_msg_ids[event_id] = result # Message will be added to event queue and # sent down serial line (via callback function _message_queue_handler()) self._message_queue.add({"TOPICS": topics, "CONTENTS": contents}) # Return the Deferred object if we need to return result def connectionMade(self): """ The initializer for the protocol. This function is called when a connection to the server (broker in our case) has been established. Keep this function LIGHT, it should not take a long time to fetch the subsystem and item IDs. The user typically shouldn't notice. I wrote a function _get_attached_items() that is called here and also when a discovery takes place. :return: None """ self._get_attached_items() return @defer.inlineCallbacks def _get_attached_items(self): """ Populates self.items with all attached item IDs NOTE: This is a subroutine of the discovery process. This method should be lightweight because it also going to be called upon connection establishment. We don't want the user waiting around forever when their device is connected. :return: """ # If we have stored systems, return them first while self._attached_item_d is not None: yield self._attached_item_d # Create a new deferred object because this is an asynchronous operation. self._attached_item_d = defer.Deferred() # The first part of the discovery protocol # is to fetch all subsystems. The reactor inside of # the embedded core should return with each subsystem as a # ID, Name pair (eg. (0, "IO_Control_board")) response = yield self.send_command(to=self.BROADCAST_SUBSYSTEM_ID, command_id=0, tx_type="BROADCAST") self._subsystem_ids = [int(response.data[0])] # print "Subsystems found:", response.data[1] # TODO: Explain this in comments d = self._attached_item_d self._attached_item_d = None d.callback(None) @staticmethod def initialize_command_maps(item_id): """ Creates the discovery command entries in the command map for the specified item ID. :param item_id: Item ID found during discovery. :return: None """ command_map[item_id] = {} property_map[item_id] = {} command_name_map[item_id] = {} command_map[item_id][RESET_ITEM] = CommandInfo("", "", "") command_map[item_id][GET_ITEM_NAME] = CommandInfo("", [], ["Item name"]) command_map[item_id][GET_ITEM_TYPE] = CommandInfo("", [], ["Item type"]) command_map[item_id][GET_COMMAND_IDS] = CommandInfo("", [], ["Command IDs"]) command_map[item_id][GET_PROPERTY_IDS] = CommandInfo("", [], ["Property IDs"]) command_map[item_id][GET_COMMAND_NAME] = CommandInfo("H", ["command_id"], ["Command name"]) command_map[item_id][GET_COMMAND_INPUT_PARAM_FORMAT] = CommandInfo("H", ["command_id"], ["Command input format"]) command_map[item_id][GET_COMMAND_INPUT_PARAM_NAMES] = CommandInfo("H", ["command_id"], ["Command input names"]) command_map[item_id][GET_COMMAND_OUTPUT_PARAM_DESC] = CommandInfo("H", ["command_id"], ["Command output names"]) command_map[item_id][GET_PROPERTY_NAME] = CommandInfo("H", ["property_id"], ["Property name"]) command_map[item_id][GET_PROPERTY_TYPE] = CommandInfo("H", ["property_id"], ["Property type"]) command_name_map[item_id]["reset_item"] = RESET_ITEM command_name_map[item_id]["get_item_name"] = GET_ITEM_NAME command_name_map[item_id]["get_item_type"] = GET_ITEM_TYPE command_name_map[item_id]["get_command_ids"] = GET_COMMAND_IDS command_name_map[item_id]["get_property_ids"] = GET_PROPERTY_IDS command_name_map[item_id]["get_command_name"] = GET_COMMAND_NAME command_name_map[item_id]["get_command_input_param_format"] = GET_COMMAND_INPUT_PARAM_FORMAT command_name_map[item_id]["get_command_input_param_names"] = GET_COMMAND_INPUT_PARAM_NAMES command_name_map[item_id]["get_command_input_param_names"] = GET_COMMAND_INPUT_PARAM_NAMES command_name_map[item_id]["get_command_output_param_desc"] = GET_COMMAND_OUTPUT_PARAM_DESC command_name_map[item_id]["get_property_name"] = GET_PROPERTY_NAME command_name_map[item_id]["get_property_type"] = GET_PROPERTY_TYPE return @defer.inlineCallbacks def get_discovery(self): """ Hitting the "discovery" button on the UI triggers this generator. Run a discovery for everything connected to this protocol and return a list of of all connected: items, messages, and endpoint types """ print "----------------------------" print "Discovery function started!" print "----------------------------" t1 = time.time() # If there is a deferred item, yield that first if self._attached_item_d is not None: yield self._attached_item_d # If we were already in the process of a discovery we should # return a deferred object. if self._in_progress: defer.returnValue(self._discovery_deferred) self._in_progress = True self.items = [] for subsystem_id in self._subsystem_ids: try: yield self._get_item_discovery_info(subsystem_id) except Exception as e: print("Exception while discovering! Skipping subsystem : " + str(subsystem_id) + "\n " + str(e)) self._in_progress = False t2 = time.time() print "Discovery took", (t2 - t1), "seconds" # At this point self.items should be populated with # the ParlayStandardItem objects that represent the items we discovered. # By calling BaseProtocol's get_discovery() function we can get that information # to the adapter and furthermore to the broker. defer.returnValue(BaseProtocol.get_discovery(self)) @staticmethod def command_cb(command_info_list, item_id, command_id, command_dropdowns, command_subfields, parlay_item, hidden=False): """ Callback function used to update the command map and parlay item dropdown menu when the command info is retrieved from the embedded device during discovery. This function is called using gatherResults which will only callback once all deferreds have been fired. :param command_info_list: Stores the command name, command input format, command input names and command output description. This information will be used to populate the ParlayStandardItem. :param item_id: 2 byte item ID of the ParlayStandardItem we will be populating :param command_id: 2 byte command ID of the command we have the information for :param command_dropdowns: dropdown field for the ParlayStandardItem :param command_subfields: subfields for each dropdown option of the ParlayStandardItem :param parlay_item: ParlayStandardItem that we will be updating :param hidden: whether or not the command will be hidden from UI :return: """ local_subfields = [] c_name = command_info_list[0] c_input_format = command_info_list[1] c_input_names = command_info_list[2] c_output_desc = command_info_list[3] command_map[item_id][command_id] = CommandInfo(c_input_format, c_input_names, c_output_desc) command_name_map[item_id][c_name] = command_id if not hidden: command_dropdowns.append((c_name, command_id)) for parameter in c_input_names: local_subfields.append(parlay_item.create_field(parameter, INPUT_TYPES.STRING, required=True)) command_subfields.append(local_subfields) return @staticmethod def property_cb(property_info_list, item_id, property_id, parlay_item): """ Callback function that populates the ParlayStandardItem parlay_item with the designated property information. :param property_info_list: Property name and property type obtained from the embedded device :param item_id: 2 byte item ID that we will be populating the ParlayStandardItem of :param property_id: 2 byte property ID that represents the property we will updating :param parlay_item: ParlayStandardItem object :return: """ property_name = property_info_list[0] property_type = property_info_list[1] property_map[item_id][property_id] = PropertyData(name=property_name, format=property_type) parlay_item.add_property(property_id, name=property_name) parlay_item.add_datastream(property_id, name=property_name + "_stream") return @staticmethod def _initialize_reactor_command_map(reactor): command_map[reactor] = {} command_map[reactor][GET_ERROR_CODES] = CommandInfo("", [], ["codes"]) command_map[reactor][GET_ERROR_STRING] = CommandInfo("H", ["code"], ["string"]) @defer.inlineCallbacks def _get_item_discovery_info(self, subsystem): """ The discovery protocol for the embedded core: GET SUBSYSTEMS IDS GET ITEM IDS GET ERROR CODE INFO GET ITEM INFORMATION Where ITEM INFORMATION is: GET ITEM NAME GET ITEM TYPE GET COMMAND IDS GET PROPERTY IDS For each command: GET COMMAND NAME GET COMMAND INPUT PARAM FORMAT GET COMMAND INPUT PARAM NAMES GET COMMAND OUTPUT PARAM DESCRIPTION For each property: GET PROPERTY NAME GET PROPERTY TYPE :param subsystem: The subsystem we will be getting the item IDs from :return: """ # Subsystem ID is the high byte of the item ID subsystem_id = subsystem # If the item was already discovered attach nothing to # the Deferred object's return value. # Otherwise add the item ID to the set of already discovered # IDs because we are about to discover it! # if item_id in self._already_discovered: # defer.returnValue({}) # else: # # add the item ID to the already_discovered set. # self._already_discovered.add(item_id) discovery = {"Subsystem ID": subsystem_id} print "Running discovery on subsystem: ", subsystem_id # Convert subsystem IDs to ints so that we can send them # back down the serial line to retrieve their attached item # For each subsystem ID, fetch the items attached to it print "Fetching items from subsystem ID: ", subsystem_id REACTOR = subsystem_id << self.SUBSYSTEM_SHIFT response = yield self.send_command(REACTOR, "DIRECT") self._item_ids = [int(item_id) for item_id in response.data] # TODO: Change to extend() to get all item IDs # Fetch error codes self._initialize_reactor_command_map(REACTOR) response = yield self.send_command(to=REACTOR, tx_type="DIRECT", command_id=GET_ERROR_CODES) self._error_codes = [int(error_code) for error_code in response.data] for error_code in self._error_codes: response = yield self.send_command(to=REACTOR, tx_type="DIRECT", command_id=GET_ERROR_STRING, params=["code"], data=[error_code]) error_code_string = response.data[0] error_code_map[error_code] = error_code_string print "---> ITEM IDS FOUND: ", self._item_ids for item_id in self._item_ids: self.adapter.subscribe(self.add_message_to_queue, TO=item_id) PCOMSerial.initialize_command_maps(item_id) for item_id in self._item_ids: response = yield self.send_command(item_id, command_id=GET_ITEM_NAME, tx_type="DIRECT") item_name = str(response.data[0]) parlay_item = ParlayStandardItem(item_id=item_id, name=item_name) response = yield self.send_command(item_id, command_id=GET_ITEM_TYPE, tx_type="DIRECT") item_type = int(response.data[0]) response = yield self.send_command(item_id, command_id=GET_COMMAND_IDS, tx_type="DIRECT") command_ids = response.data command_dropdowns = [] command_subfields = [] parlay_item.add_field('COMMAND', INPUT_TYPES.DROPDOWN, dropdown_options=command_dropdowns, dropdown_sub_fields=command_subfields) discovered_command = defer.DeferredList([]) for command_id in command_ids: # Loop through the command IDs and build the Parlay Item object # for each one command_name = self.get_command_name(item_id, command_id) command_input_format = self.get_command_input_param_format(item_id, command_id) command_input_param_names = self.get_command_input_param_names(item_id, command_id) command_output_desc = self.get_command_output_parameter_desc(item_id, command_id) discovered_command = defer.gatherResults([command_name, command_input_format, command_input_param_names, command_output_desc]) discovered_command.addCallback(PCOMSerial.command_cb, item_id=item_id, command_id=command_id, command_subfields=command_subfields, command_dropdowns=command_dropdowns, parlay_item=parlay_item, hidden=(command_id in DISCOVERY_MESSAGES)) yield discovered_command response = yield self.send_command(item_id, command_id=GET_PROPERTY_IDS, tx_type="DIRECT") property_ids = response.data discovered_property = defer.DeferredList([]) for property_id in property_ids: property_name = self.get_property_name(item_id, property_id) property_type = self.get_property_type(item_id, property_id) discovered_property = defer.gatherResults([property_name, property_type]) discovered_property.addCallback(PCOMSerial.property_cb, item_id=item_id, property_id=property_id, parlay_item=parlay_item) yield discovered_property if item_type != ITEM_TYPE_HIDDEN: self.items.append(parlay_item) print "Finished ITEM:", item_name print "Finished subsystem:", subsystem defer.returnValue(discovery) def _send_broadcast_message(self): """ Sends broadcast message to the broadcast subsystem ID stored in self.BROADCAST_SUBSYSTEM_ID :return: """ # The subsystem ID for the broadcast message is 0x80 # The high byte of the destination ID is the subsystem ID, so we should insert # 0x80 into the high byte. destination_id = self.BROADCAST_SUBSYSTEM_ID + self.EMBD_REACTOR_ID # The response code, event type, event attributes, and format string are all zero for a # broadcast message return self.send_command(to=destination_id, command_id=0, tx_type="BROADCAST") def add_message_to_queue(self, message): """ This will send a packet down the serial line. Subscribe to messages using the adapter :param message : A parlay dictionary message """ # add the message to the queue self._message_queue.add(message) def rawDataReceived(self, data): """ This function is called whenever data appears on the serial port and raw mode is turned on. Since this protocol uses line receiving, this function should never be called, so raise an excpetion if it is. :param data: :return: """ raise Exception('Using line received!') def _on_packet(self, sequence_num, ack_expected, is_ack, is_nak, msg): """ This will get called with every new serial packet. The parameters are the expanded tuple given from unstuff_packet :param sequence_num: the sequence number of the received packet :param ack _expected: Is an ack expected to this message? :param is_ack : Is this an ack? :param is_nak: Is this a nak? :param msg: The pcom message (if it is one and not an ack/nak) :type msg : PCOMMessage """ if is_ack: self._ack_window.remove(sequence_num) return elif is_nak: return # Ignore, timeout should handle the resend. parlay_msg = msg.to_json_msg() # print "---> Message to be published: ", parlay_msg self.adapter.publish(parlay_msg, self.transport.write) # If we need to ack, ACK! if ack_expected: ack = str(p_wrap(ack_nak_message(sequence_num, True))) self.transport.write(ack) # print "---> ACK MESSAGE SENT" # print [hex(ord(x)) for x in ack] # also send it to discovery listener locally self._discovery_listener(msg) def lineReceived(self, line): """ If this function is called we have received a <line> on the serial port that ended in 0x03. :param line: :return: """ # print "--->Line received was called!" # print [hex(ord(x)) for x in line] # Using byte array so unstuff can use numbers instead of strings buf = bytearray() start_byte_index = (line.rfind(START_BYTE_STR) + 1) buf += line packet_tuple = unstuff_packet(buf[start_byte_index:]) self._on_packet(*packet_tuple)