UWP loves BrainPad WIFI

Recently and as permitted by time, I started working on a project where I can control me IoT device from a UWP application which will eventually be installed on a Raspberry PI with Windows 10 IoT core. Why you may ask when there are so many services out there that can do that already. Well, my answer is simple, not every IoT devices I have should be directly connected to the internet. So, in using a WiFi enabled BrainPad (and yes, they do exists), I embarked upon this journey. So while this project is still in its early infancy and the code may not be as sophisticated as the masses, here is what it looks like thus far. I have this code in TFS but will put in out on GitHub.

WIFI Code:

using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.SerialCommunication;
using GHIElectronics.TinyCLR.Storage.Streams;
using System;
using System.Collections;
using System.Text;
using System.Threading;

namespace Lynk.IoT.TinyCLR.ESP8266Lib
{

    public enum WifiMode { Client = 1, Server = 2 }
    public enum WifiState { Disconnected, Connected, NoAP, GotIP, Busy, Unknown }
    public enum DataConnectionState
    {
        Connected,
        Ready, Closed
    }
    public delegate void PropertChangedEventHandler();
    public class Esp8266WIFI
    {
        static object _lock = new object();
        private readonly GpioPin _enablePin;
        private readonly SerialDevice _wifi;
        private readonly DataReader _dataReader;
        private readonly DataWriter _dataWriter;
        public event PropertChangedEventHandler OnWifiStateChanged;
        public event PropertChangedEventHandler OnDataConnectionChanged;
        public event PropertChangedEventHandler OnIPAddressChanged;
        public event PropertChangedEventHandler OnDataReceived;

        private string _ipAddress = "0.0.0.0";
        public string IPAddress
        {
            get { return _ipAddress; }
            set
            {
                _ipAddress = value;
                OnIPAddressChanged?.Invoke();
            }
        }

        private WifiState _wifiState = WifiState.Unknown;
        public WifiState WifiState
        {
            get { return _wifiState; }
            set { _wifiState = value; OnWifiStateChanged?.Invoke(); }
        }

        private DataConnectionState _dataConnectionState;

        public DataConnectionState DataConnectionState
        {
            get { return _dataConnectionState; }
            set
            {
                _dataConnectionState = value;
                OnDataConnectionChanged?.Invoke();
            }

        }

        private string _dataReceived;
        public string Data
        {
            get { return _dataReceived; }
            private set
            {
                _dataReceived = value;
                OnDataReceived?.Invoke();
            }
        }


        Thread _connectionWorker;

        private WifiMode _wifiMode = WifiMode.Client;

        public Esp8266WIFI(GpioPin enablePin, string portId, uint baudRate = 115200)
        {

            enablePin.SetDriveMode(GpioPinDriveMode.Output);
            _enablePin = enablePin;
            _enablePin.Write(GpioPinValue.Low);
            Thread.Sleep(200);
            _enablePin.Write(GpioPinValue.High);
            Thread.Sleep(20);
            _wifi = SerialDevice.FromId(portId);
            _wifi.BaudRate = baudRate;
            _wifi.DataBits = 8;
            _wifi.StopBits = SerialStopBitCount.One;
            _wifi.Parity = SerialParity.None;
            _wifi.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            _wifi.WriteTimeout = TimeSpan.FromMilliseconds(1000);

            _dataReader = new DataReader(_wifi.InputStream);
            _dataWriter = new DataWriter(_wifi.OutputStream);
            _connectionWorker = new Thread(ContinouslyReadWifiInput);

            _connectionWorker.Start();
            WriteCommand("AT");

        }

        public void Write(string data)
        {
            _dataWriter.WriteString(data + Environment.NewLine);
            _dataWriter.Store();
        }

        void ContinouslyReadWifiInput()
        {
            while (true)
            {
                try
                {
                    //WriteCommand("AT+CWJAP_DEF?");
                    string str = PrintCommandResponse();
                    if (str.IndexOf("WIFI CONNECTED") >= 0 || str.IndexOf("WIFI GOT IP") >= 0)
                    {
                        WifiState = WifiState.Connected;
                        if (str.IndexOf("WIFI GOT IP") >= 0)
                        {
                            WifiState = WifiState.GotIP;
                            WriteCommand("AT+CIFSR");
                            PrintCommandResponse();
                            Thread.Sleep(2000);
                            WriteCommand("AT+CIFSR");
                            str = PrintCommandResponse();
                            var splits = str.Split(new char[] { '\n' });
                            for (int i = 0; i < splits.Length; i++)
                            {
                                if (splits[i].IndexOf("+CIFSR:STAIP") >= 0)
                                {
                                    StringBuilder sb = new StringBuilder(splits[i]);
                                    sb = sb.Replace("\r", "").Replace("\"", "").Replace("+CIFSR:STAIP,", "");
                                    IPAddress = sb.ToString();
                                    break;
                                }
                            }
                        }
                    }
                    else if (str.IndexOf("No AP") >= 0 || str.IndexOf("WIFI DISCONNECT") >= 0 || str.IndexOf("busy") >= 0 || str.IndexOf("FAIL") >= 0)
                    {

                        if (str.IndexOf("No AP") >= 0)
                            WifiState = WifiState.NoAP;
                        else if (str.IndexOf("WIFI DISCONNECT") >= 0)
                            WifiState = WifiState.Disconnected;
                        else if (str.IndexOf("busy") >= 0)
                            WifiState = WifiState.Busy;
                        else if (str.IndexOf("FAIL") >= 0)
                            WifiState = WifiState.Unknown;
                        IPAddress = "0.0.0.0";
                    }
                    else if (str.IndexOf("ERROR") >= 0)
                    {

                        if (str.IndexOf("ERROR") >= 0 && str.IndexOf("CLOSED") >= 0)
                        {
                            Thread.Sleep(2000);
                            Data = str;
                        }
                        else
                        {
                            WifiState = WifiState.Unknown;
                        }
                    }
                    else
                    {
                        ReadWriteCommand(str);
                    }
                }
                catch (Exception)
                {


                }
            }
        }

        private void ReadWriteCommand(string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                return;
            }

            Data = data;
        }

        public void SetupWifiConnection(string ssid, string password, WifiMode wifiMode = WifiMode.Server)
        {
            _wifiMode = wifiMode;
            //if (_connectionWorker.IsAlive)
            //    _connectionWorker.Suspend();

            switch (wifiMode)
            {
                case WifiMode.Client:
                    WriteCommand("AT+CIPMUX=0");

                    WriteCommand("AT+CWMODE_DEF=1");
                    WriteCommand($"AT+CWJAP_DEF=\"{ssid}\",\"{password}\"");

                    break;
                case WifiMode.Server:
                    WriteCommand("AT+CIPMUX=1");
                    WriteCommand("AT+CWMODE_DEF=2");
                    WriteCommand($"AT+CWSAP_DEF=\"{ssid}\",\"{password}\",11,3");
                    break;
            }
        }

        public void Restart()
        {
            if (_connectionWorker.ThreadState == ThreadState.Running)
                _connectionWorker.Suspend();

            WriteCommand("AT");
            PrintCommandResponse();
            WriteCommand("AT+RST");
            //PrintCommandResponse();
            Thread.Sleep(5000);
            _connectionWorker.Resume();
        }

        private void WriteCommand(string command)
        {
            if (System.Diagnostics.Debugger.IsAttached)
                System.Diagnostics.Debug.WriteLine($"command - {command}");
            _dataWriter.WriteString(command + Environment.NewLine);
            _dataWriter.Store();
            _dataWriter.Flush();
            Thread.Sleep(100);
        }

        private string PrintCommandResponse()
        {
            try
            {
                var length = _dataReader.Load(2048);
                var read = _dataReader.ReadString(length);
                if (System.Diagnostics.Debugger.IsAttached)
                    System.Diagnostics.Debug.WriteLine($"response - {length} | {read}");
                return read;
            }
            catch (Exception ex)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                return ex.Message;
            }

        }

        public void OpenPassthroughConnection(string host, int port)
        {
            lock (_lock)
            {
                WriteCommand($"AT+CIPSTART=\"TCP\",\"{host}\",{port}");
                string response = PrintCommandResponse();


                if (response.IndexOf("CONNECT") >= 0)
                {
                    if (response.IndexOf("CMD=") >= 0)
                    {
                        ReadWriteCommand(response);
                    }

                    DataConnectionState = DataConnectionState.Connected;
                    WriteCommand("AT+CIPMODE=1");
                    response = PrintCommandResponse();
                    if (response.IndexOf("OK") >= 0)
                    {
                        WriteCommand($"AT+CIPSEND");
                        response = PrintCommandResponse();
                        if (response.IndexOf(">") >= 0)
                        {
                            DataConnectionState = DataConnectionState.Ready;
                        }
                    }
                    if (response.IndexOf("CMD=") >= 0)
                    {
                        ReadWriteCommand(response);
                    }
                }
                else if (response.IndexOf("ERROR") >= 0)
                {
                    Data = response;
                }


                if (response.IndexOf("CMD=") >= 0)
                {
                    ReadWriteCommand(response);
                }
            }
        }

        public void CloseConnection()
        {

        }

          }
}

BrainPad Code:

using GHIElectronics.TinyCLR.BrainPad;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Pins;
using Lynk.IoT.TinyCLR.ESP8266Lib;
using System;
using System.Collections;
using System.Text;
using System.Threading;

namespace Lynk.IoT.TinyCLR.App
{
    public class Connection
    {
        public string SSID { get; set; }
        public string Password { get; set; }
        public string Target { get; set; }
    }
    class Program
    {
     
        private static GpioPin _pin1;
        private static bool _isWiFiRestartTriggered = false;
        private static Esp8266WIFI _esp8266Client;
        private static Buzzer _buzzer;
        private static Connection _connection;

        static void Main()
        {

             _buzzer = new GHIElectronics.TinyCLR.BrainPad.Buzzer();
            _buzzer.Beep();

            // _connection = new Connection { SSID = "MOTOROLA-3258C", Password = "0661c66a03810b23b5b1", Target = "192.168.0.6" };
            _connection = new Connection { SSID = "K7 8181", Password = "123456789", Target = "192.168.43.133" };

            GpioController gpioController = GpioController.GetDefault();

            var display = new GHIElectronics.TinyCLR.BrainPad.Display();
            display.DrawSmallText(0, 0, "Hi there!");

            display.RefreshScreen();

            GpioPin CH_PD = gpioController.OpenPin(BrainPad.Expansion.GpioPin.Cs);
            _pin1 = gpioController.OpenPin(BrainPad.Expansion.GpioPin.Int);
            _pin1.SetDriveMode(GpioPinDriveMode.Output);
            _pin1.Write(GpioPinValue.High);

            InitializeEsp8266Wifi(display, CH_PD);

            InitializeButtonComponents(gpioController);

            Thread.Sleep(-1);
        }

        private static void InitializeButtonComponents(GpioController gpioController)
        {
            var leftButton = gpioController.OpenPin(BrainPad.GpioPin.ButtonLeft);
            leftButton.SetDriveMode(GpioPinDriveMode.InputPullUp);
            leftButton.ValueChanged += (s, e) =>
            {
                _buzzer.StopBuzzing();
            };
            var rightButton = gpioController.OpenPin(BrainPad.GpioPin.ButtonRight);
            rightButton.SetDriveMode(GpioPinDriveMode.InputPullUp);
            rightButton.DebounceTimeout = TimeSpan.FromMilliseconds(100);
            rightButton.ValueChanged += (s, e) =>
            {
                _buzzer.StartBuzzing(1000);
            };

            var centerButton = gpioController.OpenPin(BrainPad.GpioPin.ButtonDown);
            centerButton.SetDriveMode(GpioPinDriveMode.InputPullUp);
            //centerButton.DebounceTimeout = TimeSpan.FromMilliseconds(100);
            centerButton.ValueChanged += (s, e) =>
            {
                if (e.Edge == GpioPinEdge.RisingEdge)
                {
                    Thread.Sleep(100);

                    var read = _pin1.Read() == GpioPinValue.High ? true : false;

                    _pin1.Write(read ? GpioPinValue.Low : GpioPinValue.High);

                    read = _pin1.Read() == GpioPinValue.High ? true : false;
                    _esp8266Client.Write($"UPDATEDIGITAL={_pin1.PinNumber}:{read.ToString()}");
                }
            };
        }

        private static void InitializeEsp8266Wifi(GHIElectronics.TinyCLR.BrainPad.Display display, GpioPin CH_PD)
        {
            _esp8266Client = new Esp8266WIFI(CH_PD, BrainPad.Expansion.UartPort.Usart1);
            _esp8266Client.OnWifiStateChanged += () =>
            {
                DisplayEspState(display, _esp8266Client);
            };
            _esp8266Client.OnIPAddressChanged += () =>
            {
                DisplayEspState(display, _esp8266Client);
                CheckAndOpenConnection(_esp8266Client);
            };
            _esp8266Client.OnDataConnectionChanged += () =>
            {
                if (_esp8266Client.DataConnectionState == DataConnectionState.Ready)
                {
                    SendDeviceInfo(_esp8266Client);
                }
            };
            _esp8266Client.OnDataReceived += () =>
            {
                string data = _esp8266Client.Data;
                if (data.IndexOf("CMD=") >= 0)
                {
                    int idx = data.IndexOf("CMD=");
                    data = data.Substring(idx + 4);
                    if (data.ToUpper() == "GETDEVICEINFO")
                    {
                        SendDeviceInfo(_esp8266Client);
                    }
                }
                else if (data.IndexOf("SETDIGITAL=") >= 0)
                {
                    int idx = data.IndexOf("SETDIGITAL=");
                    data = data.Substring(idx + 11);
                    string[] splits = data.Split('|');
                    foreach (var split in splits)
                    {
                        if (string.IsNullOrEmpty(split))
                            continue;
                        var parts = split.Split(':');
                        SetDigitalPin(int.Parse(parts[0]), parts[1]);

                    }
                }
                else if (data.IndexOf("ERROR") >= 0 && data.IndexOf("CLOSED") >= 0)
                {
                    CheckAndOpenConnection(_esp8266Client);
                }

            };

            _esp8266Client.SetupWifiConnection(_connection.SSID, _connection.Password, WifiMode.Client);
        }

        private static void CheckAndOpenConnection(Esp8266WIFI esp8266Client)
        {
            if (esp8266Client.IPAddress != "0.0.0.0")
            {
                esp8266Client.OpenPassthroughConnection(_connection.Target, 10000);
            }
        }

        private static void SendDeviceInfo(Esp8266WIFI esp8266Client)
        {
            bool pin1State = _pin1.Read() == GpioPinValue.High ? true : false;
            string value = "{\"name\":\"BrainPad\",\"os\":\"TinyCLR OS\", " +
            "\"id\":\"E5363BAF-2C1B-4C6C-A92B-41A8DAFFF870\", " +
            "\"key\":\"E5363BAF-2C1B-4C6C-A92B-41A8DAFFF870\"," +
                                    " \"analogs\":[]," +
                                    "\"digitals\":" +
                                    "[" +
                                        "{\"state\": " + pin1State.ToString().ToLower() + "," +
                                        "\"number\":" + _pin1.PinNumber.ToString() + ", " +
                                        "\"name\":\"Living Room\"}" +
                                    "], \"Pwms\":[]}";
            esp8266Client.Write(value);
        }

        private static void SetDigitalPin(int pin, string state)
        {
            bool isTrue = state.ToLower().Equals("true");

            if (_pin1.PinNumber == pin)
                _pin1.Write(isTrue ? GpioPinValue.High : GpioPinValue.Low);

        }

        private static void ButtonValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            if (e.Edge == GpioPinEdge.RisingEdge)
                ;// esp8266Client.Restart();

        }

        private static void DisplayEspState(GHIElectronics.TinyCLR.BrainPad.Display display, Esp8266WIFI esp8266Client)
        {
            display.Clear();
            switch (esp8266Client.WifiState)
            {
                case WifiState.Disconnected:
                    display.DrawSmallText(0, 0, "NO WIFI");
                    break;
                case WifiState.Connected:
                case WifiState.GotIP:
                    display.DrawSmallText(0, 0, "WIFI CONNECTED");
                    break;
                case WifiState.NoAP:
                    display.DrawSmallText(0, 0, "NO AP");
                    break;
                case WifiState.Busy:
                case WifiState.Unknown:
                default:
                    display.DrawSmallText(0, 0, "ERROR");
                    break;
            }
            display.DrawSmallText(0, 10, $"IP: {esp8266Client.IPAddress}");
            display.RefreshScreen();
        }
    }
}

20180710_125709%5B1%5D

20180710_125725%5B1%5D

UWP

8 Likes

So now how does it work. The UWP application runs a socket server that listens for incoming TCP connections. The BrainPad after initializing its ESP wifi settings, establishes a TCP connection in UART Passthrough mode. Meaning, the BrainPad maintains an open connection to the UWP app and in doing so, sends the data without buffering.

Upon a successful connection, the BrainPad sends a JSON string with it capabilities along with an id and key to authenticate with the UWP application and if authentication fails, the connection is closed. The UWP application then parsed this string creating a device object and add it to its list of connected devices. It also registered the socket to be used in the two way communication.

7 Likes

Here is the git repo link containing the code to this project.

https://github.com/kirklynk/Lynk.IoT.Gateway

6 Likes