def add_node(self, node_descriptor):
        """Add a node to the sensor graph based on the description given.

        The node_descriptor must follow the sensor graph DSL and describe
        a node whose input nodes already exist.

        Args:
            node_descriptor (str): A description of the node to be added
                including its inputs, triggering conditions, processing function
                and output stream.
        """

        if self._max_nodes is not None and len(self.nodes) >= self._max_nodes:
            raise ResourceUsageError("Maximum number of nodes exceeded", max_nodes=self._max_nodes)

        node, inputs, processor = parse_node_descriptor(node_descriptor, self.model)

        in_root = False

        for i, input_data in enumerate(inputs):
            selector, trigger = input_data

            walker = self.sensor_log.create_walker(selector)

            # Constant walkers begin life initialized to 0 so they always read correctly
            if walker.selector.inexhaustible:
                walker.reading = IOTileReading(0xFFFFFFFF, walker.selector.as_stream(), 0)

            node.connect_input(i, walker, trigger)

            if selector.input and not in_root:
                self.roots.append(node)
                in_root = True  # Make sure we only add to root list once
            else:
                found = False
                for other in self.nodes:
                    if selector.matches(other.stream):
                        other.connect_output(node)
                        found = True

                if not found and selector.buffered:
                    raise NodeConnectionError("Node has input that refers to another node that has not been created yet", node_descriptor=node_descriptor, input_selector=str(selector), input_index=i)

        # Also make sure we add this node's output to any other existing node's inputs
        # this is important for constant nodes that may be written from multiple places
        # FIXME: Make sure when we emit nodes, they are topologically sorted
        for other_node in self.nodes:
            for selector, trigger in other_node.inputs:
                if selector.matches(node.stream):
                    node.connect_output(other_node)

        # Find and load the processing function for this node
        func = self.find_processing_function(processor)
        if func is None:
            raise ProcessingFunctionError("Could not find processing function in installed packages", func_name=processor)

        node.set_func(processor, func)
        self.nodes.append(node)
Add a node to the sensor graph based on the description given.

        The node_descriptor must follow the sensor graph DSL and describe
        a node whose input nodes already exist.

        Args:
            node_descriptor (str): A description of the node to be added
                including its inputs, triggering conditions, processing function
                and output stream.