Wątek przeniesiony 2017-04-27 16:52 z C/C++ przez kq.

c# - programowanie komunikacji

0

Szanowni koledzy,

Mam pewien problem. Mianowicie stworzyłem sobie aplikację komunikującą się na porcie szeregowym. Aplikacja umożliwia otwarcie jednego z dostępnych portów. Komunikacja ma odbywać się w protokole modbus RTU. Zgodnie z ideą programowania obiektowego chciałbym sobie utworzyć klasę o nazwie ModbusFrame której konstruktor byłby w stanie mi utworzyć pełną ramkę modbusową którą dalej za pomocą już istniejącej instancji userserialport byłbym w stanie wysłać.

Mam jednak problemy:

  1. Obiekt UserSerialPort aby coś wysłać należy podać pod argument tablicę byte [ ] a nie obiekt klasy ModbusFrame. Jak to "po obiektowemu" zrobić najlepiej żeby to się zazębiło ?
  2. Jak wiadomo protokół modbus ma pole "funkcja" a ja osobiście chciałbym używać funkcji 0x03 (03dec) oraz 0x10 (16dec) czyli odczyt i zapis n-rejestrów. Główny problem polega na tym że nazbyt bardzo jestem przyzwyczajony do "strukturalnego" programowania i do tego że jak piszę funkcję to ona mi zwraca pewne wartości - gdybym to pisał w takim standardzie, to funkcja formująca ramkę zwracałaby np. wskaźnik na tablicę typu byte [ ] w której to tablicy znajdowałaby się cała stworzona przez tą funkcję ramka Modbusowa. Oczywiście ktoś powie że mógłbym to robić analogicznie (a np. obiekt klasy ModbusFrame mógłby mieć pewną właściwość która by była ustawiana na właśnie wskaźnik na tablicę byte. To jednak mija się z ideą programowania obiektowego. Najlepiej byłoby zrobić to tak że mam sobie klasę ModbusFrame której obiekt załatwia mi wszystko, tzn. tworzy mi ramkę modbusową.
  3. Pojawia się kolejny problem - jak przekazywać do niego parametry i to jeszcze różną ich ilość - raz tyle a raz tyle... ? Dla funkcji 0x03 potrzeba stałej ilości parametrów, jednakże dla funkcji 0x10 w zależności od tego ile chcemy rejestrów zapisać , danych jest raz tyle a raz tyle... Jak to ugryźć w ogóle?
  4. Jak te parametry w ogóle do tego obiektu przekazywać ? Poprzez konstruktor, właściwości czy może jeszcze inaczej ?

Słuchajcie żeby była jasność, leszcz ze mnie jeszcze i tyle, nie obraźcie się. Dla mnie każda porada będzie bezcenna i za każdą z góry dziękuję.

PS. Mam już nawet napisaną funkcję która oblicza mi CRC (ostatnie dwubajtowe pole ramki)...

PS. To pierwszy z serii postów które w tonie pytającym mam zamiar publikować, myślę że przydadzą się także początkującym w przyszłości, dlatego tym bardziej prosiłbym o jakieś wskazówki.

PS. Książek się już naczytałem na temat programowania obiektowego i protokołów sporo ale praktycznie nie wpłynęło to na moje możliwości projektowania architektury systemu obiektowego.... pomocy!

1

Ja bym na twoim miejscu zobaczył jak ktoś inny to zrobił i przeanalizował jego rozwiązanie https://github.com/NModbus4/NModbus4

1

Modbus to nie moja działka, ale serial port ma obsługę prostą, jak budowa cepa. I nie należy, a wręcz nie wolno tam sie spodziewać niczego choć trochę bardziej skomplikowanego. Obiekty mają metodę ToString, a (po mojemu) Ty potrzebujesz mieć w tej swojej klasie metodę ToByteArray.

0

Dzięki. Możecie powiedzieć jeszcze czy istnieje możliwość deklaracji w klasie takiej oto np. metody:


        private UInt16[] ss()
        {
            UInt16 [] xx ={ 2, 3 };
            return xx;
        }

to znaczy zwracającej tablicę obiektów UInt16... ? W języku ANSI C to kompletnie niemożliwe - jedyną możliwością jeśli np. chciało się zwrócić coś "podwójnego" była struktura....

... i czemu wyrzuca błąd jesli zamiast UInt16 [] xx ={ 2, 3 }; wpisuję to: UInt16 [2] xx ={ 2, 3 };...? przecież rozmiar się pokrywa...

i jeszcze jedno: dlaczego jak mam w klasie prywatne pole np. takie private List<byte> frame; i przypisuję coś do niego tylko w konstruktorze tej klasy to wyskakuje nad tym polem takie ostrzeżenie: "field is never assigned to and will always have its default value null"... ?
W moim przekonaniu nie powinno go być gdyż właśnie tworząc obiekt tej klasy w przyszłości wręcz będzie przeciwnie, pole to zawsze zostanie zapisane z konstruktora....

i jeszcze jedno: zakładając że mamy tak : UInt16 [] xx ={ 2, 3 }; to czy istnieje jakaś metoda pozwalająca na dodanie do tej tablicy jeszcze kilku danych czy też rozmiar jej jest już stały i niezmienny i muszę przekopiować to sobie do nowej tablicy ?

I jeszcze jedno: dlaczego nie mogę zrobić czegoś takiego:

UInt16 [2] y;

Wyrzuca błąd... chciałem po prostu zadeklarować dwuelementową tablicę obiektów typu UInt16 i nazwać ją y. Dalej sobie będą coś do niej wpisywał...

1

Tak, możesz bez problemu zwracać tablice i inne bardziej wymyślne typy ze swoich metod.
A nawet jak się uprzeć to i dwie zmienne (poprzez ValueTuple i dekonstrukcję) też da się zwrócić.

UInt16 [2] xx ={ 2, 3 };

Bo składnia wygląda tak, że albo nadajesz długość, albo długość jest brana z tego, co znajduje się po prawej stronie.

1

A jaka jest różnica jeśli w konstruktorze mam napisane : byte x = 3; lub byte x = new byte(3);

Klasa Byte nie zawiera konstruktora przyjmującego jakikolwiek parametr, więc zapis po prawej jest niepoprawny.

I dlaczego jak mam w klasie publiczne pole np. takie public List <byte> frame; i przypisuję coś do niego tylko w konstruktorze tej klasy to wyskakuje nad tym polem takie ostrzeżenie: "field is never assigned to and will always have its default value null"... ?

Nie wiem, u mnie działa ;-)

class Test
{
    public List<byte> frame;

    public Test()
    {
        frame = new List<byte>();
    }
}

Nie ma żadnego ostrzeżenia.

Ale możliwe, że robisz coś takiego:

class Test
{
    public List<byte> frame;

    public Test()
    {
        List<byte> frame = new List<byte>();
    }
}

I wtedy ostrzeżenie będzie - bo w konstruktorze klasy Test tworzy się tutaj inna, wewnętrzna zmienna o nazwie frame, a pole pozostaje zawsze nieustalone.

I ostatnie pytanie hurtem: nie mogę nigdzie znaleźć informacji czy z poziomu konstruktora przeciążonego można wywołać konstruktora tej samej klasy (podkreślam że nie chodzi mi o wywołanie konstruktora klasy bazowej tylko jakiegoś innego konstruktora klasy w której piszę ten przeciążony) ?

Możesz. Aczkolwiek jest to trochę specyficzne. Bo możesz zrobić coś takiego:

class Test
{        
    public Test()
    {
            
    }

    public Test(int a) : this()
    {
        // zrobi swoje oraz oryginalny konstruktor
    }

    public Test(int a, string b) : this(a)
    {
        // odpali konstruktor przyjmujący inta, oraz zrobi swoje
    }
}

I czy istnieje możliwość aby publiczne pole było tablicą o rozmiarze nadawanym poprzez konstruktor ? Staram się tak zrobić ale wyrzuca błąd... Innymi słowy czy istnieje możliwość w jakiś sposób aby rozmiar takiej tablicy stanowiącej pole klasy był nadawany przy tworzeniu obiektu takiej klasy ?

Nie twórz publicznych pól ;-) Nie, serio - w C# używa się publicznych właściwości, publiczne pola są rzadkością. Ale tak, możesz tak zrobić:

class Test
{
    public int[] Tablica { get; set; }

    public Test(int wielkość)
    {
        Tablica = new int[wielkość];
    }
}
0

Dziękuję Ci za profesjonalną pomoc. Wszystko jest jasne jak słońce.

Co do tego warninga to robiłem źle bo w konstruktorze operowałem na zmiennej frame ale nie tworzyłem obiektu tego typu którego oczywiście deklarowałem jako public List<byte> frame; na początku klasy. Zastanawiam się tylko nad tym jak mi na to kompilator pozwolił nie wyrzucając błędu tylko warninga... swoją drogą pojawia się pytanie czym jest takie pole jeśli nie jest zainicjowane słówkiem new a stworzymy obiekt tej klasy ?

Jak się robi odczyt z listy nie wyrzucając jednocześnie elementu z listy ? załóżmy że mam już pętlę iterującą od 0 do lista.Count; ... ?
I jak wyciągnąć z listy ostatni element ? Sądzę że w ten sposób : lista.ElementAt(lista.Count - 1) ale chciałbym się upewnić...
No i czy jeśli chcę przeiterować po całej liście to muszę korzystać z for (int i=0; i<lista.Count; ++i) lista.Element(i) i czy mam gwarancję że indexy listy zawsze będą numerowane od zera co jeden do końca czy może być inaczej (np. z powodu tego że np. wcześniej usunąłem jakiś element listy) ? Innymi słowy : czy indexy w liście po usunięciu któregoś tam elementu się porobią nowe od 0 co jeden do końca ?

I jeszcze jedno: załóżmy że mam konstruktor zwany nr 1 i przeciążony konstruktor zwany nr 2. Załóżmy też że konstruktor nr 1 musi mieć w sobie pewną część kodu która nie ma być używana jeśli wywołujemy go z konstruktora nr 2. Dodam tylko że ta część kodu owszem ma być wykonana w konstruktorze nr 2 ale dopiero po wszystkich wcześniejszych rzeczach (mam na myśli pierwszą część kodu konstruktora nr 1 oraz kod konstruktora nr 2... Jakaś rada na to ? Bo ja widzę tylko jedną: niezależne utworzenie kon1 i kon2 z dublowaniem pewnej części kodu....

1

Jak się robi odczyt z listy nie wyrzucając jednocześnie elementu z listy ? załóżmy że mam już pętlę iterującą od 0 do lista.Count; ... ?

lista[i] czy tam lista.ElementAt(i) nie usuwa elementu z listy.

I jak wyciągnąć z listy ostatni element ? Sądzę że w ten sposób : lista.ElementAt(lista.Count - 1) ale chciałbym się upewnić...

lista[lista.Count -1]. Nie musisz używać ElementAt, wystarczy indeksator.

No i czy jeśli chcę przeiterować po całej liście to muszę korzystać z for (int i=0; i<lista.Count; ++i) lista.Element(i) i czy mam gwarancję że indexy listy zawsze będą numerowane od zera co jeden do końca czy może być inaczej (np. z powodu tego że np. wcześniej usunąłem jakiś element listy) ? Innymi słowy : czy indexy w liście po usunięciu któregoś tam elementu się porobią nowe od 0 co jeden do końca ?

Tak, indeksy zawsze będą po kolei. Masz również pętlę foreach, która zawsze przechodzi po całej kolekcji.

foreach (var item in lista)
{
    // zmienna item tutaj dostępna to element takiego typu, jaki przechowuje twoja lista
}

I jeszcze jedno: załóżmy że mam konstruktor zwany nr 1 i przeciążony konstruktor zwany nr 2. Załóżmy też że konstruktor nr 1 musi mieć w sobie pewną część kodu która nie ma być używana jeśli wywołujemy go z konstruktora nr 2. Dodam tylko że ta część kodu owszem ma być wykonana w konstruktorze nr 2 ale dopiero po wszystkich wcześniejszych rzeczach (mam na myśli pierwszą część kodu konstruktora nr 1 oraz kod konstruktora nr 2... Jakaś rada na to ? Bo ja widzę tylko jedną: niezależne utworzenie kon1 i kon2 z dublowaniem pewnej części kodu....

Zamiast dublowania kodu można go wydzielić do oddzielnej prywatnej metody.

0

Czy mógłbyś mi jeszcze podpowiedzieć taką jedną rzecz, mianowicie załóżmy że mam pewną klasę o nazwie ModbusDrv i w niej mam zdefiniowaną metodę:

    /* sending next frame private method */
    private void SendNextFrame()
    {
        ModbusReg.UserReg reg = modbusreg.listregs.ElementAt(index);
    }

która korzysta z definicji pewnej struktury innej klasy:

/* new class represent Modbus Register Table object for driver use */
public class ModbusReg
{
    public struct UserReg
    {
        public string name;
        public UInt16 address;
        public bool rw;
        public UInt16 value;
    }

    /* publiczna lista struktur typu UserReg */
    public List<UserReg> listregs;

    /* konstruktor obiektu ModbusFrame */
    public ModbusReg()
    {
        /* utworzenie nowego obiektu listy - brak elementów narazie */
        listregs = new List<UserReg>();
    }
}

Pytanie jest takie czy taka konstrukcja jest dopuszczalna i czy za każdym razem gdy metoda zostanie odpalona to zostanie jak gdyby wyciągnięty element listy spod danego indexu?
Konstrukcja nie zawiera słówka new... to dobrze jest wszystko ?

0

Tak, za każdym uruchomieniem SendNextFrame zostanie pobrany element z listy spod indeksu index. new nie jest potrzebne, bo przecież te elementy już masz, w liście, kiedyś zostały utworzone.

1 użytkowników online, w tym zalogowanych: 0, gości: 1