NodeMCU LCD connected to CharLCD package – part 2: something useful

Last part was strange, we wrote a direct driver that has no real usage. We done it only because we kept compatibility. This time we will do something more useful, WiFi content driver. We will try and do in such way that only content is send. This is more friendly for network and sanity 😛

Driver planing

This driver needs to brake common rules. Why? Buffered CharLCD has a buffer with content and uses driver to control LCD. Driver doesn’t know about content, it is only following orders.
But what we want to do is instead of doing flush() and call driver we want driver to create message and send whole buffer. We could of course write new CharLCD class that would do it but my goal is to have one control class and ability to swap drivers without changing it.
In normal cases when we are using wires (or in case of direct wifi driver) we fully control LCD. Here we won’t control it, driver is just a proxy between here and there.
How to bypass this? I’m gonna introduce two events, pre_flush and post_flush, so instead of creating one-time fix we gonna make some upgrade 🙂

Events

One more rule, we cannot change other driers 🙂 So how to do it?
Best way is to create event interface and in flush() check if driver implement it and call (or not) proper functions. Problem is what to send as parameter? Whole CharLCD? not good. Only buffer? Hmm maybe not good too but must be for now.
Interface flush_event_interface belongs to charlcd/abstract package.

class FlushEvent(object):
    """Required functions for flush events"""
    def pre_flush(self, buffer):
        """called before buffered flush"""
        raise NotImplementedError('pre_flush not implemented')

    def post_flush(self, buffer):
        """called after flush"""
        raise NotImplementedError('post_flush not implemented')

After this a new driver was created NullEvents. It is similar to Null driver but implements our new interface. Those drivers are only used in tests.

Two new tests were added and finally function flush() received four new lines, 2 just after self.dirty check:

        if issubclass(type(self.driver), FlushEvent):
            self.driver.pre_flush(self.buffer)

and two as the last lines:

       if issubclass(type(self.driver), FlushEvent):
            self.driver.post_flush(self.buffer)

With this new tests are passing and we know that events will be called when needed.

Driver

Okey, we have interface, we have tests but we do not have a new shiny driver 🙂 Lets change it. Name for driver: wifi_content. Why? Because it will only send content. No commands, no char write, only full content.
Driver is quite simple, __init__, init and send are copied from WiFi direct version, cmd, shutdown, write, char, set_mode, pre_flush stays with only one line:

pass

So where is the magic ? In one function:

    def post_flush(self, buffer):
        message = self.message.prepare_message({
            'event': 'lcd.content',
            'parameters': {
                'content': buffer,
            },
            'targets': self.node_names
        })
        self.buffer = message
        self.send()

Yes, it is such a simple piece of code.

Demo/test

So we have something that may or may not work. Only way to see effect is to write demo code:

import sys
sys.path.append("../../")
from charlcd.drivers.wifi_content import WiFi  # NOQA
from charlcd import buffered as charlcd  # NOQA
from iot_message import message


def test1():
    msg = message.Message('node-40x4')
    drv = WiFi(msg, ['node-40x4'], ('192.168.1.255', 5053))
    lcd = charlcd.CharLCD(40, 4, drv)
    lcd.init()
    lcd.write('-  Blarg !')
    lcd.write('-   Grarg !', 0, 1)
    lcd.write('-    ALIVE  !!!!', 0, 2)
    lcd.flush()

    lcd.write('/* ', 19, 0)
    lcd.write('|*|', 19, 1)
    lcd.write(' */', 19, 2)

    lcd.flush()


test1()

Ha and I see that it works. Why? How? I have small piece of code that listens for UDP at port 5053:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind(('', 5053))

print("waiting for messages")
while 1:
    try:
        message, address = s.recvfrom(1024)
        print("Message from %s: %s" % (address, message.decode()))
       # s.sendto("OK!".encode(), address)
    except:
        raise

And it shows some messages running across network:

Message from ('192.168.1.202', 42350): {"response": "", "event": "lcd.char", "node": "node-40x4", "targets": ["node-40x4"], "protocol": "iot:1", "parameters": {"content": ["-  Blarg !                              ", "-   Grarg !                             ", "-    ALIVE  !!!!                        ", "                                        "]}, "chip_id": "2c9b9d45656b45afb58b1f0a4682f91a"}

Back to lua

We have a working code in theory. To fully enjoy it in practice we need to write.. some more code 🙂 This time for NodeMCU.
We introduced new event: lcd.content. Now what we need to do is to get each line and copy it to buffer and call flush(). Sounds simple? Hopefully it is!

        if message.event == 'lcd.content' then            
            for k,v in pairs(message.parameters.content) do
                server.lcd.set_xy(0, k-1)
                server.lcd.write(v)
            end
            if server.lcd.mode == 'buffered' then
                server.lcd.flush()
            end
        end

Those lines in lcd_hd44780_server and it works! And even in direct and buffered mode on NodeMCU.

But what is in main.lua ? Only one new line:


print ("core ready")

drv = require("lcd_hd44780_i2c")
lcd = require("lcd_hd44780")

drv.addr = 0x20
drv.pins = {
RS= 4,
E1= 5,
E2= 6,
DB4= 0,
DB5= 1,
DB6= 2,
DB7= 3,
}

--lcd.lcd(drv, 40, 4)
lcd.buffered(drv, 40, 4)
lcd.cursor_visible = 0
lcd.init()

svr_lcd = require "lcd_hd44780_server"
svr_lcd.lcd = lcd

We changed lcd.lcd to lcd.buffered.

Summary

With all this we created a remote lcd screen. It is a quite nice thing. Without any wires we may display messages from anywhere, other project on esp, RPi, desktop, mobile, fridge 😀 or from.. symfony logs 🙂
Yes, this project was born because I had to click few times before I could see an error…in PHP app…

Still how to use it?

Get NodeMCU Boilerplate, copy parameters.dist.lua to parameters.lua and parameters-device.dist.lua to parameters-device.lua. Look into them and set parameters.
Next copy all to NodeMCU. Hook display via i2c or gpio (look here). Get main.lua from this post or bolierplate docs and you are ready.

Another matter is how to send content. Best is to use CharLCD or write something that would send proper message.  How to get CharLCD ?

pip install charlcd

and propably

pip install iot_message

Still it is not the end. There are some bugs that require extermination. So stay tuned 🙂

 

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