W dobie nowoczesnych języków programowania i świetnych bibliotek zadania programistyczne stają się coraz łatwiejsze. A także - i to może nie jest oczywiste - pozwalają rozwiązywać problemy w bardziej elegancji sposób.

Rozważmy typowy scenariusz programisty przetwarzającego dane z pliku XML. Niech będą to artykuły w sklepie:

<shop>
   <stock name="basement">
      <item name="bicycle" price="100" qty="3"/>
      <item name="ball" price="10" qty="5"/>
      <item name="paint" price="3" qty="20"/>
   </stock>
   <stock name="attic">
      <item name="sock" price="1" qty="100"/>
      <item name="ambrella" price="5" qty="10"/>
      <item name="tv" price="0" qty="2"/>
   </stock>
</shop>

Niech naszym zadaniem będzie znalezienie nazwy magazynu, gdzie znajdują się piłki. Można to zrobić na wiele sposobów - iterowanie po węzłach XML bądź używanie XPath. Jednak pierwsze wymaga pisania brzydkiego kodu a drugie wymaga sporej biegłości zwłaszcza przy nieco bardziej skomplikowanych zadaniach.

Użytkownicy baz danych stwierdzą, że mogą to zrobić łatwo za pomocą prostego zapytania bazodanowego. Ale wymaga to użycia innej reprezentacji danych - tabularycznej.

Rozważmy tego typu transformację (w tym przykładzie korzystamy z Pythona, biblioteki lxml do operacji na plikach XML oraz biblioteki py_linq do operacji typu Linq - Language Integrated Query):

import pprint
from collections import namedtuple
from lxml import objectify
from py_linq import Enumerable as en

def iterate_items(file):
    t = namedtuple("item", ["stock_name", "item_name", "item_qty", "item_price"])
    shop = objectify.parse(file).getroot()
    for stock in shop.stock:
        for item in stock.item:
            print("Yielding", item.get("name"))
            yield t(stock.get("name"), item.get("name"), item.get("qty"), item.get("price"))
            
q = en(iterate_items("shop.xml"))
result = q.where(
    lambda x: x.item_name == "ball").select(
    lambda x: x.stock_name).to_list()
pprint.pprint(result)

W wyniku otrzymujemy:

['basement']

Co się tutaj dzieje:

  • "drzewiaste" dane XML zostają sprowadzone do "płaskiej" (tabularycznej) postaci za pomocą iterate_items();
  • iterator służy do stworzenia obiektu umożliwiającego wyliczanie (Enumerable);
  • na tym obiekcie wywołujemy metodę kwalifikującą (where()) a na wyniku kwalifikacji metodę selekcji (select());
  • wynik konsumujemy i przekształcamy na listę.

Uwaga: kwalifikacja i selekcja odbywa się jednocześnie z generowaniem kolejnych elementów item; oznacza to, że jeśli funkcja iterate_items() jest czasochłonna, jesteśmy w stanie zacząć uzyskiwać wyniki zapytania zanim zakończy się przeglądanie danych:

q = en(iterate_items("shop.xml"))
for i in q.where(
    lambda x: int(x.item_qty) > 5).select(
    lambda x: x.item_name):
    print("Consuming", i)

otrzymujemy:

Yielding bicycle
Yielding ball
Yielding paint
Consuming paint
Yielding sock
Consuming sock
Yielding ambrella
Consuming ambrella
Yielding tv

Jak widać operacje przebiegają współbieżnie.

Kolejny przykład:

q = en(iterate_items("shop.xml"))
q.order_by(
    lambda x: int(x.item_price)).select(
    lambda x: print(f"{x.item_name}: {x.item_price}")).to_list()

i wynik:

tv: 0
sock: 1
paint: 3
ambrella: 5
ball: 10
bicycle: 100

Widać tutaj ciekawą właściwość zapytań: mogą one posiadać efekty uboczne (tutaj wywołanie print()). Co ciekawe, bez wywołania to_list() nic nie zostanie wypisane - wynik wyrażenia nie jest wtedy skonsumowany.


Stosując podejście z Linq możemy tworzyć zaawansowane konstrukty które w klasycznych algorytmach wymagały by pętli, tworzenia wyników pośrednich itd.

Na temat Linq:

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/index

Podobna technika dotycząca XML i Linq:

https://docs.microsoft.com/pl-pl/dotnet/csharp/programming-guide/concepts/linq/linq-to-xml-overview

Biblioteka Linq dla Python:

https://pypi.org/project/py_linq/

 

Mates Explorer pozwala w bardzo prosty sposób kontrolować ustawienia urządzeń MATES. Dokonuje automatycznego wykrywania wszystkich nodów w systemie a następnie, również automatycznie wyświetla wszystkie ustawienia urządzenia:

  • rejestry;
  • wejścia / wyjścia cyfrowe;
  • wejścia / wyjścia analogowe;
  • inne ustawienia specjalne.

Narzędzie pozwala modyfikować wartości rejestrów.

Program stanowi demonstrację działania potężnego API C# dla MATES które pozwala między innymi na wyliczanie wszystkich rejestrów urządzenia oraz pobierania ich właściwości (łącznie z opisami pól). Oto fragment kodu programu dodający do formatki wszystkie rejestry danej nody:

            for (int regIdx = 0; regIdx < node.Registers.Count; regIdx++)
            {
               RowDefinition rd = new RowDefinition();
               rd.Height = new GridLength(26);
               regsGridBox.RowDefinitions.Add(rd);
               var register = node.Registers[regIdx];
               register.ImmediateWrite = true;
               if (!register.ReadOnly)
               {
                  register.ImmediateRead = true;
               }
               RegisterControl rc = new RegisterControl();
               Grid.SetRow(rc, rowIdx++);
               regsGridBox.Children.Add(rc);
               rc.Register = register;
               await Task.Delay(1);
            }

A poniżej kod odświeżający wartość rejestrów:

            while (!this.stopRequest)
            {
               foreach (Node node in this.nodes)
               {
                  foreach (var reg in node.Registers.Where(r => r.ReadOnly))
                  {
                     reg.GetAll();
                  }
               }

               Thread.Sleep(1000);
            }

Dokumentacja API C# systemu MATES jest dostęna online tutaj:

media/mates/api/files.html

Do sprawdzania obecności nody MATES służą specjalne funkcje. Poniżej przykłady ich użycia w C, Python i CSharp.

#include <stdio.h>
#include "mates.h"
int main(void)
{
   MATES_HANDLE h = mates_open("proxy.mon", 1);
   if (mates_discover_single_node(h, 20) == UOS_STATUS_OK)
   {
      printf("Found node #20\n");
   }
   mates_close(h);
}

import mates

with mates.Mates("proxy.mon", 1) as m:
    for node in (20, 21, 40, 41):
        if m.discover_node(node):
            print("Discovered node #{0}".format(node))
        else:
            print("Node #{0} not found".format(node))


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Viresco.Mates;
using Viresco.Mates.Registers;
using System.Threading;

namespace unit_tests
{
   [TestClass]
   public class UnitTest1
   {
      private static Mates mates;

      /// <summary>
      /// Default configuration file name.
      /// </summary>
      private const string configFile = "mates_REMOTE.mon";
      private const int channel = 1;
      private static NodeId[] allTestedNodes = new NodeId[]
      {
         NodeId.mates_diox_mk1_1,
         NodeId.mates_dio3_mk1_1
      };

      private static List<NodeId> allDio3Nodes = new List<NodeId> { };
      private static List<NodeId> allDioxNodes = new List<NodeId> { };

      [ClassInitialize]
      public static void Initialize(TestContext context)
      {
         using (mates = new Mates(configFile, channel))
         {
            foreach (Node n in mates.DiscoverNodes(allTestedNodes))
            {
               Console.WriteLine("Found node {0}", n.NodeName);

               if (n is Dio3Node)
               {
                  allDio3Nodes.Add(n.NodeId);
               }
               else if (n is DioxNode)
               {
                  allDioxNodes.Add(n.NodeId);
               }
            }
         }
      }
   }
}

Poniższy przykład demonstruje jak za pomocą prostej klasy pomocniczej można manipulować sygnałami IO nody systemu MATES tak jakby sygnały te były elementami tabeli:

import mates

class Dio3(mates.Mates):
    """Represents one MATES-DIO3-MK1 node."""

    def __init__(self):
        super(Dio3, self).__init__("proxy.mon")
        self.node = None
        for node in (self.mates_dio3_mk1_1, self.mates_dio3_mk1_2):
            # Find first DIO3 node accessible on the bus.
            if self.discover_node(node):
                self.node = node
                break
    
    def get_din(self, channel):
        return super(Dio3, self).get_din(self.node, channel)
    
    def set_dout(self, channel, value):
        super(Dio3, self).set_dout(self.node, channel, value)
    
    def __getitem__(self, idx):
        return self.get_din(idx)
    
    def __setitem__(self, idx, val):
        self.set_dout(idx, val)

# First, make sure the node we are using is connected.
m = mates.Mates("proxy.mon", 1)
if m.discover_node(m.mates_dio3_mk1_1):
    m.close()
    
    # The with statement below handles opening and closing of 
    # the MATES handle. See Mates class constructor for details.
    with Dio3() as d:
        print("First channel: {0}".format(d[0]))
        print("Second channel: {0}".format(d[1]))
        d[2] = 0
        d[3] = 1
        # If we have loopback, then the values below will be 
        # the same as the ones set.
        print(d[2])
        print(d[3])
else:
    m.close()

Począwszy od wersji API 2.7.0.0, możliwe jest otwarcie połączenia MATES bez zewnętrznego pliku konfiguracyjnego. Dane konfiguracyjne podawane są jako tekst bezpośrednio z poziomu kodu źródłowego. Dla API w C wygląda to następująco:

#include <stdio.h>
#include "mates.h"
static char *conf = \
   "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
   "<uOS_monitor_configuration>"
   " <local_address>1</local_address>"
   " <named_pipe map=\"1\">"
   "  <name>\\\\RAKIETA\\pipe\\MATES_PROXY_PIPE</name>"
   "  <timeout>5000</timeout>"
   " </named_pipe>"
   "</uOS_monitor_configuration>";
int main(void)
{
   int success = 0;
   char buf[0x200];
   int node = mates_ucc_mk1_1;
   MATES_HANDLE h = mates_open_buffer(conf, 1);
   if (mates_discover_single_node(h, node) == UOS_STATUS_OK)
   {
      if (mates_node_info(h, node, buf, sizeof(buf)) == UOS_STATUS_OK)
      {
         success++;
         printf("Found node #%d:\n", node);
         printf("%s\n", buf);
      }
      else
      {
         mates_print_errors();
      }
      mates_close(h);
      return (success == 1 ? 0 : -1);
   }
   else
   {
      return (0);
   }
}

W C# dodatkowo możemy korzystać z klasy reprezentującej konfigurację uOS_monitor:

using System;
using System.IO;
using System.Xml;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Viresco.Mates;
using Viresco.uOS_monitor.Config;
using Viresco.Utils.Xml;

namespace unit_tests
{
   [TestClass]
   public class mates_test_15
   {
      [TestMethod]
      public void TestMethod()
      {
         Configuration cfg = new Configuration();
         cfg.LocalAddress = 1;
         var pipe = new NamedPipe();
         pipe.Name = @"\\RAKIETA\pipe\MATES_PROXY_PIPE";
         pipe.Timeout = 5000;
         pipe.Map = 1;
         cfg.NamedPipe.Add(pipe);
                  
         using (Mates mates = Mates.FromBuffer(Serialization.ClassToXmlString(cfg), 1))
         {
            try
            {
               UccNode ucc = mates.NewUccNode(NodeId.mates_ucc_mk1_1);
               ucc.AlcdWrite(0, 0, "mates_open_buffer");
            }
            catch (Exception ex)
            {
               Console.Write("Cannot discover node: " + ex + "\n");
            }
         }
      }
   }
}

 

 

Poniższy prosty program w Pythonie demonstruje możliwości systemu MATES. Po podłączenia nody typu MATES-UCC-MK1 możemy zacząć rejestrować temperaturę w pliku CSV:

# -*- coding: utf-8 -*-
"""
@file
@brief Temperature recorder in a 10 line Python program.
"""
import mates
import time

with mates.Mates("proxy.mon", 1) as m:
    if m.discover_node(m.mates_ucc_mk1_1):
        with open("temperature.csv", "a") as f:
            for i in range(100):
                f.write("{0};{1}\n".format(time.time(), m.get_adc(m.mates_ucc_mk1_1, 5)))
                time.sleep(0.1)