ProxyLCD upgrades and content formatters

To use our beautiful ILi9325 display with ProxyLCD we need to manually edit settings.ini. This is not a good way:) Why is that? Because we cannot set a proper size. We are using a selector with predefined values. We need to change it to two text fields.

And we need theย formatters, what is that you may ask? You shall see ๐Ÿ™‚

Formatters

When we get a content it may contain special character like the new line. We need to do something with them.
We can strip it or move a cursor to the new line. On small LCD it is better to strip but on our big, it is better to go to the new line.
Let’s create a class that aggregate all formatters. I say all, but for start, we will have as much as two ๐Ÿ™‚ Clean formatter that strips special chars and simple formatter that will try and do a nice output on ILI.

We need an interface to force functions implementation in formatters:

import abc


class Formatter(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def format(self, content):
        """return formatted content"""
        pass

    @abc.abstractmethod
    def get_name(self):
        """formatter name"""
        pass

Next, a stub for clean formatter:

from abstract.formatter import Formatter


class CleanFormatter(Formatter):
    def format(self, content):
        """return formatted content"""
        return content

    def get_name(self):
        """formatter name"""
        return "clean"

and for simple formatter:

from abstract.formatter import Formatter


class SimpleFormatter(Formatter):

    def format(self, content):
        """return formatted content"""
        return content

    def get_name(self):
        """formatter name"""
        return "simple"

I was thinking about a way to use formatters. My first thought was to register them in StreamContent class but I think we need a better way. Maybe a service that would take content and required formatter, next it would call proper formatter and return a result.
This way all things connected to formatter are separated from the core and it is a module on its own.
In main we will do:

def initialize_formatter():
    formatter = Formatter()
    formatter.add(CleanFormatter())
    formatter.add(SimpleFormatter())
    return formatter
(..)
    stream_content = StreamContent(config, initialize_formatter())

Now we have formatters in StreamContent.
But how to tell which LCD works with which formatter? This is a task for the configuration ๐Ÿ™‚ For now, I added:

formatter=clean

to all entries and upgraded Config and Display classes to handle this.

It is time to use formatters. First body for clean one:

    def format(self, content):
        """return formatted content"""
        pattern = re.compile(r'\s+')
        return pattern.sub(' ', content)

and hook for execution in StreamContent class:

    def _send_stream(self, content):
        """stream content to nodes with stream enabled"""
        displays = self.lcd_repository.find({'can_stream': True})
        for display in displays:          
            display.stream(self.formatter.format(display.formatter, content))

What is the difference?

changed into:

And this:

into this:

It is a big difference ๐Ÿ™‚

Display form

We need to change add/update form. We will add formatter selector and we will change size select to two text fields. So we can set any dimension we need.
Block that defines size changed to:

        size_label = QLabel('Size (w*h)')
        size_x = QLineEdit()
        size_y = QLineEdit()
        size_x.setMaximumWidth(40)
        size_y.setMaximumWidth(40)
        layout.addWidget(size_label, 3, 0)
        layout.addWidget(size_x, 3, 1)
        layout.addWidget(size_y, 3, 2)
        self.widgets['size'] = {
            'x': size_x,
            'y': size_y
        }

As for the formatter selector. Zonk, we have no reference to it in form view, and it is not in the main view. So quick change and add it as a parameter to constructors.
And we have it in form view ๐Ÿ™‚

    formatter_label = QLabel('Formatter')
    formatter = QComboBox()
    formatter.addItems(self.formatters.get_names())
    layout.addWidget(formatter_label, 5, 0)
    layout.addWidget(formatter, 5, 1, 1, 2)
    self.widgets['formatter'] = formatter

Finally, add populate during an edit, and save the value and it is ready.

List

Our main view is a list of available LCDs and their settings. Lets’ add a formatter name there.
In ViewMain class, function _init_content, for loop we will add:

    item = QTableWidgetItem(display.formatter)
    item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable)
    table_widget.setItem(row, 4, item)

And change headers to:

    table_widget.setHorizontalHeaderLabels(["Name", "Node name", "Size", "Stream", "Formatter"])

Another small feature added.

More formatters

From what I see we need a formatter that won’t change anything. Let’s name it theย none :

from abstract.formatter import Formatter


class NoneFormatter(Formatter):

    def format(self, content):
        """return formatted content"""
        return content

    def get_name(self):
        """formatter name"""
        return "none"

Register it in main:

formatter.add(NoneFormatter())

Now, as we have a big screen let’s work on simple formatter. Its task is to parse new lines and tabs.
Of course, it is not as easy as I thought. The formatter has no access to LCD so it cannot do anything :/ It doesn’t know the size.
Time to refactor ๐Ÿ™‚

We will pass formatter to stream in Display instead of calling it before the stream.

    def _send_stream(self, content):
        """stream content to nodes with stream enabled"""
        displays = self.lcd_repository.find({'can_stream': True})

        for display in displays:
            display.stream(
                content,
                self.formatter.formatters[display.formatter]
            )

This forced us to change stream function:

    def stream(self, content, formatter=None):
        """send data to lcd"""
        if formatter is not None:
            content = formatter.format(content, self)
        self.lcd.stream(content)
        self.lcd.flush()

Ok, we are ready to work on SimpleFormater.

    def format(self, content, display):
        """return formatted content"""
        lines = []
        pattern = re.compile(r'\s+')
        for line in content.splitlines():
            line = pattern.sub(' ', line)
            if len(line) < display.lcd.get_width():
                lines.append(line.ljust(display.lcd.get_width()))
            else:
                chunks = math.ceil(len(line)/display.lcd.get_width())
                for chunk in range(chunks):
                    subline = line[
                        chunk*display.lcd.get_width():chunk*display.lcd.get_width()+display.lcd.get_width()
                    ]
                    lines.append(subline.ljust(display.lcd.get_width()))

        return "".join(lines)

What is going on there?
We split out input content to lines. Next, each line is compressed by removing multiple spaces, tabulators and other special chars.
If it doesn’t fit into the buffer, the line is split by length into a few lines and they go to buffer.
If necessary we add spaces to fill to the length of LCD. Finally, we join buffer into the big string and return it.

The result is perfect:

Aftermatch

A keen eye may see a flaw in our software (ok, many flaws but I’m talking about the one I have in mind ๐Ÿ˜› ). When we use Symfony’s dump(), the content is passed to bundle formatter and it changes array and objects into strings. But it does it without adding new lines to the content. So our simple formatter works only with content that already has special characters.
Maybe it is a good idea to play a little with Symfony’s formatter and transform objects in a better way? But not this time ๐Ÿ™‚

Summary

We made nice upgrades in ProxyLCD. We can now add formatters to use with remote LCD node. With formatters, we can clean unnecessary spaces, tabs, new lines or use them to show beautiful content on the ILI display.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s