7
\$\begingroup\$

I recently finished writing an HoloLens proof-of-concept program. It involves generating a user interface based on a connection with an OPC server. OPC involves industrial PLCs, one or more PLCs send their data to an OPC server, and the OPC-client connects and process all the information in the form of JSON.

An example is this:

string json = "{\"PC_Station\": [{\"PLC_1\": {\"DB1\": {\"test123\": 30}, \"STOP\": false, \"START\": true, \"Start_1\": false, \"Stop_1\": true, \"Led1\": false, \"Led2\": false, \"Led3\": false, \"Counter\": 3880, \"Sliderval\": 60}}]}";

The code I've made generates a canvas with a panel. All the variables inside the JSON code get added to the panel in the form of a UI/Image. The function updateTags() continuously updates the data that gets sent using UDP. It functions like I want it to, but I'm wondering if there's a way to reduce the amount of code.

using UnityEngine;
using System;
using System.IO;
using System.Text;
using System.Linq;
using HoloToolkit.Unity;
using System.Collections.Generic;
using UnityEngine.UI;
using Newtonsoft.Json;
using System.Collections;
using Newtonsoft.Json.Linq;
using System.Text.RegularExpressions;
#if !UNITY_EDITOR
using Windows.Networking.Sockets;
using Windows.Networking.Connectivity;
using Windows.Networking;

#endif

public class UDPCommunication : Singleton<UDPCommunication>
{
    // Connection variables
    private string port = "8000";
    private string externalIP = "172.16.24.251";
    private string externalPort = "8001";
    public static int size = 0;
    public static List<Dictionary<string, string>> abc = new List<Dictionary<string, string>>();
    public static List<string> varz;
    public GameObject mainGameobject;
    public GameObject upd1;
    public GameObject upd2;
    public GameObject upd3;
    public GameObject upd4;
    public GameObject canvas;
    public GameObject Panel;
    public GameObject image;
    public GameObject imagetext;
    public GameObject numbertext;
    public Image testimg;
    private GameObject getImageTags;
    private GameObject getNumberTags;
    private GameObject[] canvases;
    private GameObject[] panels;
    private GameObject[] tiles;
    private GameObject[] texts;
    private float scaler = 0.0125f;
    // UI/Text elements
    const string TurnOn = "on";
    private uint sliderVal;
    // Sets up a Queue
    private string receivedmsg;
    public readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action>();
    private void Awake()
    {

    }

    IEnumerator updateTags()
    {
        yield return new WaitUntil(() => receivedmsg != null);

        //string json = "{\"PC_Station\": [{\"PLC_1\": {\"DB1\": {\"test123\": 30}, \"STOP\": false, \"START\": true, \"Start_1\": false, \"Stop_1\": true, \"Led1\": false, \"Led2\": false, \"Led3\": false, \"Counter\": 3880, \"Sliderval\": 60}}]}";
        //string json1 = "{\"PC_Station\": [{\"PLC_0\": {\"DB1\": {\"test123\": 0}, \"STOP\": false,\"Frap\": false, \"START\": false, \"Start_1\": false, \"Stop_1\": false, \"Led1\": true, \"Led2\": false, \"Led3\": true, \"Counter\": 4002, \"Sliderval\": 0}},{\"PLC_1\": {\"DB1\": {\"test123\": 55}, \"STOP\": false, \"START\": false, \"Start_1\": false, \"Stop_1\": false, \"Led1\": true, \"Led2\": false, \"Led3\": true, \"Counter\": 4002, \"Sliderval\": 0}}]}";

        while (true)
        {
            var data = JToken.Parse(receivedmsg);
            foreach (var value in data)
            {
                foreach (JArray arr in value)
                {
                    for (int i = 0; i < arr.Count; i++)
                    {
                        foreach (var item in arr[i])
                        {
                            var itemproperties = item.Parent;
                            foreach (JToken token in itemproperties)
                            {
                                var prop = token as JProperty;
                                var plc = (JObject)prop.Value;
                                string canvass = "Canvas" + i.ToString();
                                upd1 = transform.Find(canvass).gameObject;
                                upd2 = transform.Find("Canvas" + i + "/Panel").gameObject;
                                foreach (KeyValuePair<string, JToken> val in plc)
                                {
                                    var plcvarkey = val.Key;
                                    var plcvarvalue = val.Value;
                                    if (plcvarvalue is JObject)
                                    {
                                        foreach (KeyValuePair<string, JToken> plcvvobj in (JObject)plcvarvalue)
                                        {
                                            upd4 = transform.Find("Canvas" + i + "/Panel/" + plcvvobj.Key + "/" + plcvvobj.Key + "value").gameObject;
                                            upd4.GetComponent<Text>().text = plcvvobj.Value.ToString();
                                        }

                                    }
                                    else
                                    {
                                        upd3 = transform.Find("Canvas" + i + "/Panel/" + plcvarkey).gameObject;
                                        //upd3.GetComponent<Image>().color = Color.green;
                                        if (plcvarvalue.ToString() == "True")
                                        {
                                            upd3.GetComponent<Image>().color = Color.green;
                                        }
                                        if (plcvarvalue.ToString() == "False")
                                        {
                                            upd3.GetComponent<Image>().color = Color.red;
                                        }
                                        if (Regex.IsMatch(plcvarvalue.ToString(), @"^\d+$"))
                                        {
                                            upd4 = transform.Find("Canvas" + i + "/Panel/" + plcvarkey + "/" + plcvarkey + "value").gameObject;
                                            upd4.GetComponent<Text>().text = plcvarvalue.ToString();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            yield return new WaitForSeconds(0.5f);
        }
    }
#if !UNITY_EDITOR
    // Socket initialization
    DatagramSocket socket;
#endif
#if !UNITY_EDITOR


    IEnumerator initGUI()
    {
        yield return new WaitUntil(() => receivedmsg != null);
        createUserInterface(receivedmsg);
        Debug.Log("left initgui");
    }
    // use this for initialization
    async void Start()
    {
        /*StartCoroutine(SendSliderValue());
        Button btn_on = led1_button_on.GetComponent<Button>();
        Button btn_off = led1_button_off.GetComponent<Button>();
        Button btn1_on = led3_button_on.GetComponent<Button>();
        Button btn1_off = led3_button_off.GetComponent<Button>();

        btn_on.onClick.AddListener(delegate {TaskWithParameters("Led1 on"); });
        btn_off.onClick.AddListener(delegate {TaskWithParameters("Led1 off"); });
        btn1_on.onClick.AddListener(delegate {TaskWithParameters("Led3 on"); });
        btn1_off.onClick.AddListener(delegate {TaskWithParameters("Led3 off"); });*/
        //string json = "{\"PC_Station\": [{\"PLC_0\": {\"DB1\": {\"test123\": 0}, \"STOP\": false,\"Frap\": false, \"START\": false, \"Start_1\": false, \"Stop_1\": false, \"Led1\": true, \"Led2\": false, \"Led3\": true, \"Counter\": 4002, \"Sliderval\": 0}},{\"PLC_1\": {\"DB1\": {\"test123\": 55}, \"STOP\": false, \"START\": false, \"Start_1\": false, \"Stop_1\": false, \"Led1\": true, \"Led2\": false, \"Led3\": true, \"Counter\": 4002, \"Sliderval\": 0}}]}";

        Debug.Log("Waiting for a connection...");
        socket = new DatagramSocket();
        socket.MessageReceived += Socket_MessageReceived;

        //createUserInterface(receivedmsg);
        HostName IP = null;
        try
        {
            var icp = NetworkInformation.GetInternetConnectionProfile();

            IP = Windows.Networking.Connectivity.NetworkInformation.GetHostNames()
            .SingleOrDefault(
                hn =>
                    hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
                    == icp.NetworkAdapter.NetworkAdapterId);

            await socket.BindEndpointAsync(IP, port);
        }
        catch (Exception e)
        {
            Debug.Log(e.ToString());
            Debug.Log(SocketError.GetStatus(e.HResult).ToString());
            return;
        }
        SendMessage("test");
        StartCoroutine(initGUI());
        StartCoroutine(updateTags());
    }


    void TaskWithParameters(string message)
    {
        Debug.Log("sending Message");
        SendMessage(message);
    }

    private async System.Threading.Tasks.Task SendMessage(string message)
    {
        using (var stream = await socket.GetOutputStreamAsync(new Windows.Networking.HostName(externalIP), externalPort))
        {
            using (var writer = new Windows.Storage.Streams.DataWriter(stream))
            {
                var data = Encoding.UTF8.GetBytes(message);
                writer.WriteBytes(data);
                await writer.StoreAsync();
                Debug.Log("Sent: " + message);
            }
        }
    }
#else


    // Use this for initialization.
    void Start()
    {

    }
#endif
    // Update is called once per frame.
    void Update()
    {
        // Dequeues items until there are no more items on the queue.
        while (ExecuteOnMainThread.Count > 0)
        {
            ExecuteOnMainThread.Dequeue().Invoke();
        }
    }

    IEnumerator SendSliderValue()
    {
        Debug.Log("entered slider class");
        GameObject theplayer = GameObject.Find("Hololens-Slider");
        TubeSliderManager test = theplayer.GetComponent<TubeSliderManager>();
        while (true)
        {
            sliderVal = test.CurrentValue;
            string s = "Slidervalue" + sliderVal.ToString();
            SendMessage(s);
            yield return new WaitForSeconds(0.5f);
        }
    }


#if !UNITY_EDITOR

    //this method gets called when a message is received
    private async void Socket_MessageReceived(Windows.Networking.Sockets.DatagramSocket sender, Windows.Networking.Sockets.DatagramSocketMessageReceivedEventArgs args)
    {
        // Read the received message.
        Stream streamIn = args.GetDataStream().AsStreamForRead();
        StreamReader reader = new StreamReader(streamIn);
        receivedmsg = await reader.ReadLineAsync();
        //Debug.Log("MESSAGE: " + message);
        // if the count is zero, the message will be relayed to the setStuff method, which processes the string continuously.
        // The message contains a JSON string which is received from the server.
        if (ExecuteOnMainThread.Count == 0)
        {
            ExecuteOnMainThread.Enqueue(() =>
            {
                //Debug.Log(receivedmsg);
                //pass msg to function here
            });
        }
    }
#endif

    public void createUserInterface(string jsonstring)
    {
        Debug.Log("entered create UI");
        var root = JToken.Parse(jsonstring);
        IterateJtoken(root);
        canvases = new GameObject[abc.Count];
        panels = new GameObject[abc.Count];
        for (int i = 0; i < abc.Count; i++)
        {
            canvases[i] = Instantiate(canvas, transform.position, transform.rotation);
            canvases[i].name = "Canvas" + i;
            canvases[i].transform.SetParent(mainGameobject.transform, false);
            canvases[i].transform.position += new Vector3(i * 14, 0, 30);
            panels[i] = Instantiate(Panel, transform.position, transform.rotation);
            panels[i].name = "Panel";
            panels[i].transform.SetParent(canvases[i].transform, false);
            for (int z = 0; z < abc[i].Count; z++)
            {
                tiles = new GameObject[abc[i].Count];
                texts = new GameObject[abc[i].Count];
                tiles[z] = Instantiate(image, transform.position, transform.rotation);
                tiles[z].name = abc[i].ElementAt(z).Key;
                tiles[z].transform.SetParent(panels[i].transform, false);
                texts[z] = Instantiate(imagetext, transform.position, transform.rotation);
                texts[z].name = abc[i].ElementAt(z).Key + "text";
                texts[z].transform.SetParent(tiles[z].transform, false);
                texts[z].GetComponent<Text>().text = abc[i].ElementAt(z).Key;
                texts[z].transform.position += new Vector3(44 * scaler, -4 * scaler, 0);
                if (Regex.IsMatch(abc[i].ElementAt(z).Value, @"^\d+$"))
                {
                    numbertext = Instantiate(imagetext, transform.position, transform.rotation);
                    numbertext.name = abc[i].ElementAt(z).Key + "value";
                    numbertext.transform.SetParent(tiles[z].transform, false);
                    texts[z].transform.position += new Vector3(0, 19.5f * scaler, 0);
                    numbertext.transform.position += new Vector3(77 * scaler, -18.5f * scaler, 0);
                }
            }
        }
    }



    public void IterateJtoken(JToken jtoken)
    {
        foreach (var value in jtoken)
        {
            foreach (JArray test in value)
            {
                for (int i = 0; i < test.Count; i++)
                {
                    foreach (var item in test[i])
                    {
                        var itemproperties = item.Parent;
                        foreach (JToken token in itemproperties)
                        {
                            if (token is JProperty)
                            {
                                var prop = token as JProperty;
                                //Console.WriteLine(prop.Name);           //PLC name
                                var plc = (JObject)prop.Value;
                                Dictionary<string, string> variables = new Dictionary<string, string>();
                                foreach (KeyValuePair<string, JToken> val in plc)
                                {
                                    if (val.Value is JObject)
                                    {
                                        JObject nestedobj = (JObject)val.Value;
                                        foreach (JProperty nestedvariables in nestedobj.Properties())
                                        {
                                            size++;
                                            //variables[nestedvariables.Name] = nestedvariables.Value.ToString();
                                            var nestedVariableName = nestedvariables.Name;
                                            var nestedVariableValue = nestedvariables.Value;
                                            variables.Add(nestedVariableName, nestedVariableValue.ToString());
                                            //Console.WriteLine(nestedVariableName+" "+nestedVariableValue);
                                        }

                                    }
                                    else
                                    {
                                        size++;
                                        //variables[val.Key] = val.Value.ToString();
                                        var variableName = val.Key;
                                        var variableValue = val.Value;
                                        variables.Add(variableName, variableValue.ToString());
                                        //Console.WriteLine(variableName+" "+variableValue);
                                    }

                                }
                                abc.Add(new Dictionary<string, string>(variables));
                            }
                        }
                    }
                }
            }
        }
    }
}

The code's actually quite simple. For the function createUserInterface, I generate a few public Gameobjects, canvas, Panel, image and imagetext. At the start of the function, the function IterateJtoken() gets called. This function deserializes the JSON String I mentioned in the above example. It fills up the public List of dictionaries which I've named 'abc'. Based on how the List is filled, the createUserInterface builds a user interface with the following hierarchy (manager is the empty gameobject I attach the script to):

enter image description here

As for the updateTags function, the public string I've called receivedmsg gets updated every 0.5 seconds. For instance, if a tag is true, the image turns green and for false it turns red.

An example:

enter image description here

As you can see in the picture above, if I input the following string:

string json1 = "{\"PC_Station\": [{\"PLC_0\": {\"DB1\": {\"test123\": 0}, \"STOP\": false,\"Frap\": false, \"START\": false, \"Start_1\": false, \"Stop_1\": false, \"Led1\": true, \"Led2\": false, \"Led3\": true, \"Counter\": 4002, \"Sliderval\": 0}},{\"PLC_1\": {\"DB1\": {\"test123\": 55}, \"STOP\": false, \"START\": false, \"Start_1\": false, \"Stop_1\": false, \"Led1\": true, \"Led2\": false, \"Led3\": true, \"Counter\": 4002, \"Sliderval\": 0}}]}";

The code will generate 2 canvases with each their own panel and all the variables like 'test123' as images.

I would like to know if there is a way to make the methods updateTags(), createUserInterface() and IterateJToken() more efficiently.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Naming

The class is named UDPCommunication, but it does createUserInterface, among other things. This shows that some of the code should probably be part of another class, or that at least the class name does not fit well.


abc, varz, test, upd1, prop, plc etc. are not good variable names, because they do not tell me what they contain and what they are for.

scaler, sliderVal, receivedMsg are not as problematic, but still not very good choices. It is better to be more explicit and to avoid shortening words, because it harms readability. Better names would be uiScale, sliderValue, and receivedMessage.


In C# the convention for method names is UpperCamelCase, a.k.a. PascalCase, rather than lowerCamelCase. So e.g. createUserInterface should be CreateUserInterface.

Comments

You have quite a lot of lines of code that are commented out. That can make it hard to judge later on why they were commented out, and whether they will still be needed in the future. If they are obsolete, better remove them altogether. If you decide later on that you need some of it again, it will still be under source control.

Empty methods

You can remove the empty Awake() and Start() methods, since they don't do anything, and Unity does not require them.

Access modifiers

Some of your methods are lacking an access modifier. While the default modifier for methods is private, by convention it is usually not left out, because it is more explicit and more readable. Also, some of your methods are explicitly private, and it is most important to be consistent.

Nested generic collections

When you have a generic type like this

public static List<Dictionary<string, string>> abc = new List<Dictionary<string, string>>();

it can be worth considering writing a custom class for that type, because it may indicate that it is more than just a generic collection of generic collections.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.