ProxyLCD – upgrades in Symfony bundle

I need to focus on my additional project in PHP. That means Doton and GfxLCD lib must wait but we will use this time to upgrade ProxyLCD project.

What is ProxyLCD? It is remote LCD that works on NodeMCU, stand alone desktop app in Python and Symfony’s bundle.
Bundle listens to dump() command and when a dump is called it parse object or array to a string, connects to an app and sends it. App gets this content and sends it to added remote LCDs.

Read more about ProxyLCD on the right column:)

Planning

Currently, the message is sent and displayed as is, appended to the current content. This is a stream mode.
But I want to start on cleared display and from the top with each request.
We can achieve this by adding control packets with simple commands like clear, new line, set position.
This requires a packet stream. In such packet, content is encoded into JSON.
With this, we will send clear command on kernel request and the ProxyLCD app will handle it.

Sending commands

Currently, bundle’s config looks like this:

kosci_proxy_lcd:
  proxy_ip: 192.168.1.102
  dump:
    enabled: true
    mode: stream

We will add another boolean variable, clear_on_request. With this when the proper event is called, the command would be sent to ProxyLCD and it will clear attached displays.
We still have no target ability and that’s why all displays gonna be cleared.

kosci_proxy_lcd:
  proxy_ip: 192.168.1.102
  clear_on_request: true
  (..)

In Configuration.php we define allowed parameters. To handle our new one, we need to add:

(..)
->booleanNode('clear_on_request')->defaultFalse()->end()
(..)

Ok. now we need to teach our bundle how and when to use it. How? By adding the proper listener:

    public function __construct(
        $enabled,
        $clear_on_request,
        ProxyLCDInterface $proxyLCD
    )
    {
        $this->enabled = $enabled;
        $this->proxyLCD = $proxyLCD;
        $this->clear_on_request = $clear_on_request;
    }

    public function onKernelRequest()
    {
        if (!$this->enabled || !$this->clear_on_request) {
            return;
        }

        $this->proxyLCD->clearDisplays();
    }

When we have enabled debugger and clear on request, listener calls method clearDisplays on proxyLCD service.

Now just to implement this method 🙂

    public function clearDisplays()
    {
        if (!$this->ip) {
            return;
        }

        $this->sendCommand('clear');
    }

    private function sendCommand($command)
    {

    }

And finally, we can focus on packet implementation. How to create them? How it should look alike? And so.
Why it is so important? Because ProxyLCD app gets a string and needs to guess if this is a command or stream.
For sure packet is a JSON document. What about the structure?
I go with two or three fields, protocol (always proxylcd), command and optional parameter. If the protocol matches proxylcd our app would know that this is a valid command.

private function sendCommand($command)
    {
        $packet = [
            'command' => $command,
            'protocol' => 'proxylcd'
        ];
        $this->sendPacket(
            $content = json_encode($packet)
        );
    }

We can also extract sendPacket function:

    private function sendPacket($content)
    {
        try {
            $fp = fsockopen($this->ip, $this->port, $errno, $errstr, 10);
            fwrite($fp, $content);
            fclose($fp);
        } catch (\Exception $exception) {
            throw Proxy::conectionFailed($this->ip, $this->port, $exception->getMessage());
        }
    }

Ok, we are sending command but currently, it is ignored.

Handling commands

Before we handle a command we need to detect it 🙂
So back to lcd-proxy Python app. There we have a StreamContent service and function stream.
This function gets content and passes it to LCDs. First, we will change its name for something better, like handle_stream, next we will clear and refactor it:

    def handle_stream(self, content):
        """send content to display"""
        content = content.strip()
        message = self._decode(content)
        if message and 'protocol' in message and message['protocol'] == 'proxylcd':
            self._handle_command(message)
        else:
            self._send_stream(content)

    def _handle_command(self, command):
        """handle a message"""
        if 'command' not in command:
            return

        if command['command'] == 'clear':
            displays = self.lcd_repository.find({'can_stream': True})
            for display in displays:
                display.lcd.buffer_clear()
                display.lcd.set_xy(0,0)

We added clear command, it clears buffer and set the cursor position to (0,0).

When we load a Symfony page in dev mode (with profiler) we will see some nasty problem. With each request, we send clear command, so our page sends clear command, displays dump, and the profiler (or some other) subrequest is called. This clears screen again!

What can we do with this?
We can fix this in the bundle, my idea is to remember the time and call clean only if 10s passed from the last request. This is, of course, configurable by request_length variable.

    public function onKernelRequest(GetResponseEvent $event)
    {
        if (!$event->isMasterRequest() || !$this->enabled || !$this->clearOnRequest) {
            return;
        }
        $session = $event->getRequest()->getSession();
        $current = $session->get('proxylcd.time', 0);
        if ($current + $this->requestLength < time()) { $this->proxyLCD->clearDisplays();
        }
        $session->set('proxylcd.time', time());
    }

This time it looks good and works well.

Summary

This is a small upgrade but I already love it! Debugging is easier and I do not have to guess where is my output. There is one problem left, char display 40×4 is a way to small. But stay tuned 🙂 I have a remedy for this.

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