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/

 

Program mates_proxy.exe służy do pośredniczenia w komunikacji między aplikacjami klienckimi a nodami MATES. Umożliwia utworzenie więcej niż jednego połączenia do systemu MATES (np. kanał aplikacji głównej i kanał monitorowania stanu lub debugowania).

Do wersji 2.7.0.0 program ten zakładał predefiniowaną listę nodów MATES które wykrywał i o których wyświetlał informację. Miało to jeden negatywny skutek: czas wykrywania był długi.

Począwszy od wersji 2.7.1.0 udostępniony został nowy interfejs linii poleceń. Umożliwia on jawne podanie listy nodów do wykrycia co znacznie przyspiesza start programu. Jest także możliwość podania innych parametrów, np. nazwy pliku konfiguracyjnego. Oto listing pomocy podręcznej uzyskiwanej poleceniem --help:

VIRESCO mates_proxy rev. 2.7.1.4056

Usage: mates_proxy [-h] [--version] -c <int> -n <int> [-n <int>]... <file>
  -h, --help                Print this help and exit
  --version                 Print program version
  -c, --channel=<int>       The channel number to use
  -n, --nodes=<int>         The MATES nodes numbers to discover
  <file>                    The MATES configuration file

Referencje:

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);
               }
            }
         }
      }
   }
}

Nareszcie - uOS_monitor jest w stanie komunikować się z nodami urządzeń MATES bez potrzeby stosowania mapy symboli. Od wersji 3.3.x.x program wprowadza wsparcie dla API biblioteki mates.dll. Nowe funkcjonalności dostępne są jako callback w menu ustawień kontrolek:

  • Powyższy przykład zawiera konfigurację kontrolki typu level meter podpiętej do wartości rejestru PRD03 nody typu MATES-UCC-MK1. Istotne elementy to:
  • Type: callback - ta wartość jest używana dla bezpośredniego dostępu do MATES
  • Target: jedna z wartości /mates/*
  • Offset: numer nody MATES
  • Argument: w tym przypadku zawiera numer rejestru PRD03 (por. podręcznik użytkownika systemu MATES)

Inny przykład:

 

Oraz przykładowy ekran synoptyczny dla MATES-UCC-MK1:

Dodatkowe informacje:

 

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");
            }
         }
      }
   }
}

 

 

Począwszy od wersji 3.3.x.x, uOS_monitor posiada możliwość obrotu wszystkich kontrolek o dowolny kąt. Pozwala to lepiej gospodarować dostępną powierzchnią ekranu:

Nowa funkcjonalność wiąże się z dodaniem właściwości Rotation do wszystkiich obsługinwanych kontrolek:

Nowa wersja jest kompatybilna wstecz (nie wymaga żadnej zmiany w plikach konfiguracyjnych).

Razem możliwością obrotu kontrolek program zyskał też możliwość zmiany ułożenia (wyrównanie prawa, lewa, środek) elementów tekstowych (dotyczy części kontrolek):

 

Wersję demo można pobrać tutaj:

 

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

Począwszy od wersji MK7 sterownik UCC posiada szybkie elektroniczne zabezpieczenie wyjść cyfrowych. Próg zadziałania został ustalony na 1 A. Czas zadziałania około 60 μs. W zależności od oprogramowania urządzenie może podjąć normalną pracę zaraz po ustąpieniu przyczyny zwarcia lub przejść w tryb zatrzymania.

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()

Film przedstawia użycie uOS_monitor podczas weryfikowania poprawności działania programu sterującego elektrycznym sterownikiem drzwi odskokowo - przesuwnych.

Użyta jest następująca konfiguracja: do komputera podłączone są dwa urządzenia (za pomocą RS-232). Jedno z nich to sterownik drzwi, drugie - urządzenie imitujące główny komputer sterujący w pociągu. Oba urządzenia są ze sobą połączone za pomocą interfejsu CAN.

Na filmie widzimy kolejno:

  1. Badanie zachowania sterownika przy wysterowaniu jego wejść. Przycisk <Inputs OVR> służy do zastąpienia wartości prawdziwych wejść sygnałami wysyłanymi z uOS_monitor (wartości sygnałów wyjściowych pochodzą z pola <Fake inp>).
  2. Za pomocą owijacza uOS_monitor dla Python łączymy się z drugim urządzeniem (symulującym komputer główny) i ustalamy wartość słowa sterującego wysyłanego przez CAN (efekt widać w polu <DCMD> oraz oknie prawego terminala).
  3. Wymuszony zostaje błąd geometrii (lampki Geometry w <Tfaults>, <Sfaults> i <Rfaults>).
  4. Wymuszony zostaje błąd zbyt wysokiego napięcia (lampki Umains hi w <Tfaults>, <Sfaults> i <Rfaults>).
  5. Zostaje pobrany log z urządzenia.
  6. Log zostaje wyświetlony w postaci strony HTML.

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)