Are Nextion Displays supported?

There are nice displays from a company called Nextion (https://nextion.tech).
They can be pre-programmed by a kind of display editor, and afterwards they can be controlled e.g. via serial connection using some special strings.
I have such a display, which I used before with an arduino. I still have the C++ code for creating the necessary control strings, which I used at that time.
Now I want to use it with the Meadow F7v2. Of course I could easily adapt my old code to C#, but wanted to ask if these Nextion displays are already supported by the foundation code. In this case I would not have to do this at my own.

Are there some methods in the foundation to support these displays?

Hi otto,

We don’t have a driver for the Nextion displays but I’d love to see them supported. If you adapt your C++ to C# we’d love to add it to Meadow.Foundation.

And by implementing the IGraphicsDisplay interface in the driver, it will get MicroGraphics support.
Meadow.Foundation/IGraphicsDisplay.cs at main · WildernessLabs/Meadow.Foundation · GitHub

Let me know if you have any questions.

Cheers

Adrian

Hi Adrian,
in the meantime I made it work. But it’s only a very small program for doing some tests (I’m a Meadow beginner).
The good news is that my display (a nx4832t035) was 3.3 V tolerant, so I didn’t need to use some converters.
There are some great oo libraries (C++) out there (DOWNLOAD - Nextion) like GitHub - itead/ITEADLIB_Arduino_Nextion
Unfortunately I won’t have time to adopt (and test) them to C# in the near future, but in a few days I’ll post at least the poor simple functions I used for my tests, so that the underlying mechanism can be seen.

Here’s some source code and background information how I used my old Nextion display (with serial interface) with the Meadow.

The Nextion has many different capabilities, like built-in scripting, adaptable widgets that can be controlled dynamically. To use this in real world applications, a serial two-ways-communication needs to be implemented, like it is in TCP. One side sends a request and expects an answer, often within some time limit.

Luckily, for my use case (doing some tests with the meadow), this wasn’t necessary, I just wanted to display strings with information on the display and needed some buttons to press. For this, a one-way-communication is sufficient, I just sent some strings to the display for setting the labels, or, after a button on the display was pressed, the display sent some special chars to my program. No answer was necessary.

The approach I used is very simple, I’m hesitating to show it here, because it is so poor. But the purpose was only to make it work for me, not to write some Nextion C# library or adapt an existing one to C#. If you should have the need to implement two-way-communication (and this need will come sooner as you expect), I suggest to take a look at the great ITEAD C++ library on Github, which I mentioned also in my post some days ago: GitHub - itead/ITEADLIB_Arduino_Nextion
Maybe it would make sense to get in touch with the guys there, maybe they would be interested in adapting it, I don’t know. If not, or if you want to write some small functions for getting some answering strings from the nextion by yourself, it is worth to take a look at the methods recvRetNumber() and recvRetString() in the source file NexHardware.cpp there, to get inspired.

Concerning my use case: I made a small program, consisting in two source files, MeadowApp.cs and NextionDisplay.cs.
On the display, there are some labels to display the program version, some status information, and the current time. There are three buttons to turn the LED on the meadow to red, green or off. That’s all.
The Nextion display gets its power via the 5V pin on the nextion, which (hopefully, I don’t know for sure) is directly connected to the 5V from the USB. Its RX and TX lines are connected to the appropriate (opposite) COM4 Pins on the Meadow.

Here’s the code. I wish you much fun and success, it’s great how easy even complex interfaces can be built with the Nextion.

==== Begin MeadowApp.cs ====

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using Meadow;
using Meadow.Devices;
using Meadow.Hardware;
using Nextion;

namespace NextionSimpleTest
{
    // Change F7FeatherV2 to F7FeatherV1 for V1.x boards
    public class MeadowApp : App<F7FeatherV2>
    {
        // consts etc.
        const string Version = "1.10";
        const string NextionPortName = "COM4";
        const int NextionBaudRate = 115200;

        // to deal with our current mode
        enum Modes { Off, Red, Green }
        Modes mode = Modes.Off;

        // indexes of nextion pages (we only have one...)
        const int Page_Main = 0;

        // names of nextion labels
        const string Name_tVersLabel = "tVersLabel";
        const string Name_tVersValue = "tVersValue";
        const string Name_tStatus = "tStatus";
        const string Name_tTime = "tTime";

        // ids of nextion controls
        const int Id_tVersLabel = 2;
        const int Id_tVersValue = 3;
        const int Id_tStatus = 4;
        const int Id_tTime = 7;
        const int Id_bRed = 5;
        const int Id_bGreen = 1;
        const int Id_bOff = 6;

        // hardware
        IDigitalOutputPort redLed;
        IDigitalOutputPort greenLed;
        NextionDisplay nextionDisplay;

        // set up Meadow and Nextion
        public override Task Initialize()
        {
            Console.WriteLine(new string('-', 30));
            Console.WriteLine("Initialize, Version " + Version);
            Console.WriteLine(new string('=', 30));

            redLed = Device.CreateDigitalOutputPort(Device.Pins.OnboardLedRed);
            greenLed = Device.CreateDigitalOutputPort(Device.Pins.OnboardLedGreen);

            try
            {
                var port = Device.CreateSerialPort(Device.PlatformOS.GetSerialPortName(NextionPortName), NextionBaudRate, 8, Meadow.Hardware.Parity.None, Meadow.Hardware.StopBits.One, 512);
                nextionDisplay = new NextionDisplay(port, NextionCommandHandler);
                Console.WriteLine(string.Format("Nextion display on port {0} successfully connected.", NextionPortName));
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("ERROR: could not connect to Nextion Display on serial port {0}. Exception: {1}", NextionPortName, ex.Message));
            }

            nextionDisplay.SendCommand_SetPage(Page_Main);
            nextionDisplay.SendCommand_SetText(Name_tVersValue, Version);
            RefreshStatusText();

            return base.Initialize();
        }

        // show on the display what is going on
        void RefreshStatusText()
        {
            string text;

            switch (mode)
            {
                case Modes.Red:
                    text = "red led is on";
                    break;
                case Modes.Green:
                    text = "green led is on";
                    break;
                case Modes.Off:
                    text = "led is off";
                    break;
                default:
                    text = "(unknown mode)";
                    break;
            }

            nextionDisplay.SendCommand_SetText(Name_tStatus, text);
        }

        // for debugging
        void ComSerialPort_MessageReceived(object sender, SerialMessageData e)
        {
            Console.WriteLine($"Message received: {e.GetMessageString(Encoding.ASCII)}");
        }

        // e.g. key pressed on Nextion
        void NextionCommandHandler(int pageId, int controlId, int data)
        {
            Console.WriteLine(string.Format("Nextion command received, pageId: {0}, controlId: {1}", pageId, controlId));
            if (pageId == Page_Main)
            {
                switch (controlId)
                {
                    case Id_bRed:
                        mode = Modes.Red;
                        Console.WriteLine("Mode set to red");
                        break;
                    case Id_bGreen:
                        mode = Modes.Green;
                        Console.WriteLine("Mode set to green");
                        break;
                    case Id_bOff:
                        mode = Modes.Off;
                        Console.WriteLine("Mode set to off");
                        break;
                    default:
                        // unknown id (not yet handled)
                        break;
                }
                RefreshStatusText();
            }
            else
            {
                // handle other pages here
            }
        }

        public override async Task Run()
        {
            Console.WriteLine("Run...");

            await Work();
        }

        // loop for refreshing the Nextion ports etc. due to the current mode
        Modes oldMode = Modes.Off;
        string oldTimeString = null;
        Task Work()
        {
            Console.WriteLine("Working started...");

            while (true)
            {
                // evtl. change leds state
                if (mode != oldMode)
                {
                    switch (mode)
                    {
                        case Modes.Red:
                            redLed.State = true;
                            greenLed.State = false;
                            break;
                        case Modes.Green:
                            redLed.State = false;
                            greenLed.State = true;
                            break;
                        default:
                            redLed.State = false;
                            greenLed.State = false;
                            break;
                    }
                    oldMode = mode;
                }

                // evtl. change displayed time
                var timeString = DateTime.Now.ToString("HH:mm:ss");
                if (timeString != oldTimeString)
                {
                    nextionDisplay.SendCommand_SetText(Name_tTime, timeString, false);
                    oldTimeString = timeString;
                }

                // give (e.g. serial) events some time
                Thread.Sleep(100);
            }
        }
    }
}

==== End MeadowApp.cs ====

==== Begin NextionDisplay.cs ====

using System;
using System.Collections.Generic;
using System.Text;
using Meadow;
using Meadow.Hardware;

namespace Nextion
{
    public class NextionDisplay : IDisposable
    {
        ISerialPort Port { get; set; }
        List<byte> ReceivedBytes { get; set; } = new List<byte>();

        public delegate void OnCommand(int pageId, int controlId, int data);
        event OnCommand OnCommandHandler;

        public NextionDisplay(ISerialPort port, OnCommand onCommandHandler)
        {
            OnCommandHandler = onCommandHandler;

            this.Port = port;
            try
            {
                Port.Open();
                Port.DataReceived += SerialPort_DataReceived;
                Console.WriteLine(string.Format("Serial port {0} for NEXTION successfully opened.", Port));
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("ERROR: could not open serial port {0} for NEXTION. Exception: {1}", Port, ex.Message));
                throw;
            }
        }

        void Disconnect()
        {
            if (Port?.IsOpen ?? false)
                Port?.Close();
            Port = null;
        }

        private void SerialPort_DataReceived(object sender, Meadow.Hardware.SerialDataReceivedEventArgs e)
        {
            Console.WriteLine("Stop 11");
            var buffer = new byte[1];

            var sp = (ISerialPort)sender;
            while (sp.BytesToRead > 0)
            {
                var nb = sp.Read(buffer, 0, 1);
                if (nb != 1)
                {
                    Console.WriteLine("Read-Error: " + nb);
                    throw new Exception("0805-1811-01-933");
                }
                var b = buffer[0];
                Console.WriteLine(string.Format("Read: {0} ({0:x})", b));
                ReceivedBytes.Add(b);
                if (CommandReceived(out var commandBytes))
                {
                    ExecCommand(commandBytes);
                    ReceivedBytes.Clear();
                }
            }
            Console.WriteLine("Stop 19");
        }

        // eAAAyyy
        // 0123456
        bool CommandReceived(out List<byte> commandBytes)
        {
            Console.WriteLine("Stop 21");
            commandBytes = null;

            var len = ReceivedBytes.Count;
            if (len < 4)
                return false;
            if (ReceivedBytes[len - 3] != 0xff || ReceivedBytes[len - 2] != 0xff || ReceivedBytes[len - 1] != 0xff)
                return false;

            commandBytes = new List<byte>();
            for (var i = 1; i < len - 3; i++)
                commandBytes.Add(ReceivedBytes[i]);
            Console.WriteLine("Stop 29");
            return true;
        }

        void ExecCommand(List<byte> commandBytes)
        {
            var len = commandBytes.Count;
            Console.WriteLine("Stop 31, len=" + len);

            if (len < 2) // happens if the communication with the Nextion was started, and he sends 26 and 3 times 0xff
                return;

            var page = (int)commandBytes[0];
            var control = (int)commandBytes[1];
            var data = len >= 3 ? (int)commandBytes[2] : 0;

            Console.WriteLine(string.Format("Stop 33, page={0}, control={1}, data={2}", page, control, data));
            OnCommandHandler?.Invoke(page, control, data);
        }

        public void Dispose()
        {
            Disconnect();
        }

        public void SendCommand(string s, bool writeInfoToConsole = true)
        {
            if (Port != null)
            {
                if (Port.IsOpen)
                {
                    Port.Write(Encoding.ASCII.GetBytes(s));
                    Port.Write(new byte[] { 0xff, 0xff, 0xff }, 0, 3);
                    if (writeInfoToConsole)
                        Console.WriteLine(string.Format("Sent to Nextion: \"{0}\"", s));
                }
                else
                    Console.WriteLine("ERROR: Port not open");
            }
            else
                Console.WriteLine("ERROR: Port not initialized");
        }

        public void SendCommand_SetPage(int page, bool writeInfoToConsole = true)
        {
            SendCommand(string.Format("page {0}", page), writeInfoToConsole);
        }

        public void SendCommand_SetText(string controlName, string newValue, bool writeInfoToConsole = true)
        {
            SendCommand(string.Format("{0}.txt=\"{1}\"", controlName, newValue), writeInfoToConsole);
        }

        public void SendCommand_ShowHide(int control, bool show, bool writeInfoToConsole = true)
        {
            SendCommand(string.Format("vis {0},{1}", control, show ? 1 : 0), writeInfoToConsole);
        }
    }
}

==== End NextionDisplay.cs ====

Amazing! We should try and turn this into a full Meadow.Foundation driver. I’ll look into ordering a few displays.