Σελίδα 3 από 3 ΠρώτοΠρώτο 123
Εμφάνιση αποτελεσμάτων 21 μέχρι 27 από 27

Θέμα: Προγραμματισμός με object Pascal (Delphi)

  1. #21
    Μέλος
    Ημερομηνία εγγραφής
    Mar 2012
    Θέση
    Athens, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    99

    Ηλεκτρονικό κατάστημα ειδών θέρμανσης και κλιματισμού

    TCP/IP Server
    Θα υλοποιήσω ένα απλό TCP/IP πολυνηματικό διακομιστή (Server) χρησιμοποιώντας τα TServerSocket & TClientSocket components.
    (Απαιτείται να εγκατασταθεί το package /bin/dclsockets70.bpl)
    Στο http://delphi.about.com/od/networking/l/aa112602a.htm θα βρείτε περισσότερες πληροφορίες για την χρήση των συγκεκριμένων components.
    Επίσης στο http://www.overbyte.be/frame_index.h...ducts/ics.html θα βρείτε μια διαφορετική υλοποίηση από Indy και Delphi socket components.

    Για πελάτες (clients) θα χρησιμοποιήσουμε

    1. Ένα απλό browser που θα «χτυπάει» μία http διεύθυνση
    Κώδικας:
    http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
    και στην περίπτωσή μας http://127.0.0.1:1024

    H παραπάνω HTTP κλήση εμφανίζεται στον Server σαν το παρακάτω κείμενο
    Κώδικας:
    GET / HTTP/1.1
    Host: 127.0.0.1:1024
    User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/15.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en,el;q=0.8,el-gr;q=0.5,en-us;q=0.3
    Accept-Encoding: gzip, deflate
    DNT: 1
    Connection: keep-alive
    Ο Server απαντά
    Κώδικας:
    HTTP/1.1 200 OK
    Content-Length: 11
    Hello World
    Connection: close
    [empty line]


    2. Το telnet στο οποίο θα επιστρέφεται ότι στέλνουμε (echo).
    Start/Run Cmd
    Στο Dos box πληκτρολογούμε telnet 127.0.0.1 1024
    Πληκτρολογούμε «Hello» και το αποστέλλουμε στον Server με Enter
    Κώδικας:
    Client  ---------- initate connection -----------> Server
    ...
    Client  <---------------command------------------- Server
    Client  ----------------response-----------------> Server
    ...
    Client  <---------------command------------------- Server
    Client  ----------------response-----------------> Server
    Πρέπει να υλοποιηθεί το RFC 854, και δεν μπορεί να συνυπάρξει με το modbus ή το Web server


    3. To modbus client.
    Θα πρέπει να επιλέξουμε για θύρα που ακούει ο Server την 502.

    Κώδικας:
    |Modbus Application Protocol (MBAP) Header | Protocol Data Unit (PDU)|
    |------------------------------------------+-------------------------|
    |Transaction |Protocol  | Length  | Unit   | Function   |   Data     |
    |Identifier  |Identifier| Field   | ID     | Code       |            |
    |--------------------------------------------------------------------|
    | (2 bytes)  |(2 bytes) |(2 bytes)|(1 byte)|(1 byte)    | Μεταβλητό  |
    |------------------------------------------+-------------------------|
    Υλοποίησα, στον TCP/Server, ένα modbus client που υποστηρίζει μόνο τις παρακάτω functions.

    03 (0x03) Read Holding Registers
    Βλέπε MODBUS Application Protocol Specification V1.1b σελίδα 19/51
    http://www.modbus.org/docs/Modbus_Ap...ocol_V1_1b.pdf

    06 (0x06) Write Single Register
    Βλέπε. MODBUS Application Protocol Specification V1.1b σελίδα



    Statefull
    Ένας WebServer, δημιουργεί για κάθε νέα σύνδεση ένα SessionId που το επιστρέφει στον Client σαν Cookie. Με κλειδί αυτό το SessionId, ο WebServer, συσχετίζει όλη τα δεδομένα του συγκεκριμένου client (user name, db connection κτλ)
    Ο ίδιος client, στην επόμενη επικοινωνία του, αποστέλλει το SessionId ώστε να επαναχρησιμοποιήσει τα δεδομένα που όρισε σε προηγούμενη κλήση (statefull).

    Περιγραφή του προγράμματος.
    Ο server ακούει στην πόρτα 1024 και για κάθε νέα σύνδεση πελάτη δημιουργεί ένα ξεχωριστό thread που θα χειρίζεται την επικοινωνία μεταξύ τους (ServerType= stThreadBlocking). Μόλις αποσταλεί η απάντηση στον πελάτη, το νήμα τελειώνει την εργασία του και επιστρέφει στο pool. Η επόμενη κλήση από τον ίδιο πελάτη δεν θα συσχετισθεί με καμία προηγούμενη (stateless).
    Δημιουργούνται νήματα (threads) κλάσης που κληρονομεί από την TServerClientThread και επιστρέφονται στο Server μέσω του event OnGetThread.
    Κώδικας:
    procedure TFrmServer.ServerGetThread(Sender: TObject;  ClientSocket: TServerClientWinSocket;   var SocketThread: TServerClientThread);
    begin
     SocketThread:=TSrvThread.Create(ClientSocket, Handle);
    end;
    
    TSrvThread=class(TServerClientThread)
               private
                fHandle:THandle;
    ...
               protected
                procedure ClientExecute; override; 
               public
                constructor Create(aSocket:TServerClientWinSocket;
               aHandle:THandle);
               end;
    Στην procedure TSrvThread.ClientExecute; διαβάζουμε το εισερχόμενο TCP/IP μήνυμα.

    Η ρουτίνα RequestType το κατηγοριοποιεί σαν rtHTTP, rtText, rtModBus ή rtBinary και απαντάει ανάλογα.

    Στο http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html περιγράφεται ότι η πρώτη γραμμή ορίζεται σαν

    Κώδικας:
    Request-Line   = Method SP Request-URI SP HTTP-Version CRLF
    Όπου Method         = "OPTIONS"                ; Section 9.2
                          | "GET"                    ; Section 9.3
                          | "HEAD"                   ; Section 9.4
                          | "POST"                   ; Section 9.5
                          | "PUT"                    ; Section 9.6
                          | "DELETE"                 ; Section 9.7
                          | "TRACE"                  ; Section 9.8
                          | "CONNECT"                ; Section 9.9
                          | extension-method
           extension-method = token
    Στο πρόγραμμα κατηγοριοποιώ την κλήση σαν http αν περιέχει το HTTP/1. και ξεκινάει με τις λέξεις GET ή HEAD.

    Ο χειρισμός του telnet, είναι διαφορετικός. To socket παραμένει «ανοικτό» έως να έρθει κάποιος control χαρακτήρας.

    Το πρόγραμμα χρειάζεται επεκτάσεις εάν κάποιος θέλει να κάνει πραγματική δουλειά με αυτό.

    Η πρώτη και σημαντικότερη είναι η υλοποίηση μιας κλάσης TModbus που θα υλοποιεί το modbus πρωτόκολλο έτσι ώστε ο κώδικας (C μεταφιεσμένη σε Pascal) να γίνει συντηρήσιμος.

    Κώδικας:
      
      rtModBus : begin
             case pbFC^ of
               $03 : begin // mbfReadHoldingRegs = $03;
                      pa:=pword(FRequest);
                      pd:=pa;
                      inc(pa, 4); // points to Start Address
                      inc(pd, 5); // point to count
                      move(FRequest^,data[0],8);
                      cnt:=swap(pd^);
                      data[ 8]:=cnt*2;
                      pd:=pword(@data[9]);
                      for i:=0 to pred(cnt) do begin
                        w:=SendMessage(fHandle,REG_GET,swap(pa^)+i,0);
                        pd^:=swap(w);
                        inc(pd);
                       end;
                      size:=8+1+cnt*2;
                      data[5]:=2 + 1 + cnt*2;
                      ClientSocket.SendBuf(data,size);
                     end;
    Μπορείτε να κατεβάσετε την εφαρμογή και το source από το
    http://www.filefactory.com/file/dftv...n/Server01_rar

    Και παραφράζοντας τον μαύρο πειρατή (Αστερίξ στη Κορσική σελ. 19)
    Bugus humanum est

    Καλό φθινόπωρο

  2. #22
    Μέλος
    Ημερομηνία εγγραφής
    Mar 2012
    Θέση
    Athens, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    99
    Σωστή ή λανθασμένη υλοποίηση ;

    Μετά από αρκετά χρόνια στην δουλειά βλέπω το λάθος από μακριά. Και από μια παράξενη σύμπτωση όταν ελέγχω την δουλειά κάποιου άλλου σχεδόν πάντα «σκοντάφτω» επάνω του.

    Σαν να είναι εκεί και να μου κλείνει το μάτι και να μου λέει «προσπέρνα με».

    Στην υλοποίηση του Server έμπλεξα τις λειτουργίες του server με τον χειρισμό των μηνυμάτων. Ο Server διαχειρίζεται tcp/ip συνδέσεις, και «περνάει» τα μηνύματα σε κατάλληλους χειριστές (handler threads).
    Εκεί τα πράγματα πρέπει να είναι επίσης καθαρά και την αποκωδικοποίηση της ερώτησης και την κωδικοποίηση της απάντησης πρέπει να την κάνει μια άλλη HTTP ή Telnet ή TModbus κλάση.

    Η απάντηση λοιπόν είναι απλή. Ξαναγράψτο. Νέα σχεδίαση, υλοποίηση και τεκμηρίωση.

    TModbus
    Το modbus είναι ένα πρωτόκολλο ανταλλαγής ερωτήσεων & απαντήσεων και παρέχει μία client/server επικοινωνία μεταξύ συσκευών συνδεδεμένων μέσω TCP/IP. Κάθε ερώτημα περιγράφεται από ένα κωδικό (function code FC). Το modbus πρωτόκολλο απεικονίζει τις διευθύνσεις και τα δεδομένα με το πιο σημαντικό byte πρώτο (big Endian).
    Οι 4 κύριοι πίνακες που χρησιμοποιεί το Modbus είναι
    Διακριτές είσοδοι, απεικονίζονται σε bits και μπορούμε μόνο να τις διαβάσουμε.
    Coils, (solid-state relay) απεικονίζονται σε bits και μπορούμε να τις διαβάσουμε & να γράψουμε
    Input Registers, απεικονίζονται σε 16-bit word, μπορούμε μόνο να τις διαβάσουμε.
    Holding Registers, απεικονίζονται σε 16-bit word, μπορούμε να τις διαβάσουμε & να γράψουμε
    Οι βασικές λειτουργίες είναι
    (0x01) Read Coils
    (0x02) Read Discrete Inputs
    (0x03) Read Holding Registers
    (0x04) Read Input Registers
    (0x05) Write Single Coil
    (0x06) Write Single Register

    O modbus Master ρωτάει ή καθορίζει τιμές για εισόδους ή εξόδους ενός PLC. To PCL είναι slave και απαντάει στα ερωτήματα του Master.
    Στην δική μας περίπτωση υλοποιούμε αμφοτέρους.

    Ο Master/client χρησιμοποιεί ένα socket component για την TCP/IP σύνδεση με τον Server/ Slave. Κατασκευάζει ένα object της κλάσης TModbus και καλεί τις ρουτίνες που υλοποιούν τις βασικές λειτουργίες.

    Τα αποτελέσματα εμφανίζονται σε array properties.

    Κώδικας:
    Modbus:=TModbus.Create(FSocket);
    If Modbus.ReadHoldingRegisters(1,1) then
     Modbus.HoldingRegs[1]
    έχει την ζητούμενη τιμή.

    Ο Server/Slave έχει ήδη λάβει το TCP/IP μήνυμα και πρέπει να αποφασίσει τον τύπο του.
    Χρησιμοποιώντας τον 2ο constructor δημιουργεί ένα object και ελέγχει την ορθότητα του μηνύματος. Σε περίπτωση λάθους στην δομή του μηνύματος, δημιουργεί exception.

    Η κλάση TModbus περικλείει (encapsulates) την δομή ενός modbus μηνύματος και ο παρακάτω κώδικας (ισάξιος μόνο για τον Chuck http://blog.magicsoftware.com/2011/0...rogrammer.html )
    Κώδικας:
      // check for ModBus request
      if FReqSize>=12 then begin
       pwTI :=pword(p);    // 0..1 Transaction Identifier
       pwPI :=pword(p+2);  // 2..3 Protocol Identifier
       pwlf :=pword(p+4);  // 4..5 Length Field
       pbUID:=pbyte(p+6);  // 6    Unit ID
                           //      MB_IGNORE_UNITID = 255
       pbFC :=pbyte(p+7);  // 7;   Function Code
                           //      mbfReadHoldingRegs = $03;
                           //      mbfWriteOneReg = $06;
                           // 8;   Data
    
       if ((pbFC^=$06) or (pbFC^=$03)) and (pwlf^=swap($0006)) then begin
        result:=rtModBus;
        exit;
       end;
      end;
    αντικαθίσταται με
    Κώδικας:
      try
       FModbus:=TModbus.Create(FRequest^,FReqSize);
       FModbus.OnGetData:=ModbusGetData;
       FModbus.OnSetData:=ModbusSetData;
       result:=rtModBus;
       exit;
      except
       Log('Not modbus message');
      end;
    Και ο επίσης «ανδρικός» κώδικας
    Κώδικας:
    rtModBus : begin
             case pbFC^ of
               $03 : begin // mbfReadHoldingRegs = $03;
                      pa:=pword(FRequest);
                      pd:=pa;
                      inc(pa, 4); // points to Start Address
                      inc(pd, 5); // point to count
                      move(FRequest^,data[0],8);
                      cnt:=swap(pd^);
                      data[ 8]:=cnt*2;
                      pd:=pword(@data[9]);
                      for i:=0 to pred(cnt) do begin
                        w:=SendMessage(fHandle,REG_GET,swap(pa^)+i,0);
                        pd^:=swap(w);
                        inc(pd);
                       end;
                      size:=8+1+cnt*2;
                      data[5]:=2 + 1 + cnt*2;
                      ClientSocket.SendBuf(data,size);
                     end;
    αντικαθίσταται με
    Κώδικας:
           rtModBus : begin
                       FModbus.PrepareResponse;
                       FModbus.Respond(ClientSocket);
                       FreeAndNil(FModbus);
                      end
    και η κλάση ΤModbus ακολουθεί.

    Κώδικας:
     TBytes = array of byte;
    
     TMBAP = packed record { 7 bytes }
               TrnId : word;
               ProtocolIdent: word;
               Len:word;
               UnitId:byte;
              end;
    
     TPDU  = packed record { 253 bytes }
               FC  : byte;
               Data: packed record case integer of
                      0 : (b: array[0..251] of byte);
                      1 : (w: array[0..125] of word);
                      2 : (o: packed record
                                len : byte;
                                w   : array[0..124] of word;
                                b   : byte;
                               end)
                     end;
              end;
    
     TADU  = packed record // 260 bytes
               MBAP : TMBAP;
               PDU  : TPDU;
              end;
    
    |Modbus Application Protocol (MBAP) Header | Protocol Data Unit (PDU)|
    |------------------------------------------+-------------------------|
    |Transaction |Protocol  | Length  | Unit   | Function   |   Data     |
    |Identifier  |Identifier| Field   | ID     | Code       |            |
    |--------------------------------------------------------------------|
    | (2 bytes)  |(2 bytes) |(2 bytes)|(1 byte)|(1 byte)    | Μεταβλητό  |
    |------------------------------------------+-------------------------|
    
     TModbus = class
               Private
                 ...
               Public
                // Client Side
                constructor Create(aSocket:TCustomWinSocket); overload;
                // Server Side
                constructor Create(const Request; size:word); overload;  
                procedure   PrepareResponse;
                procedure   Respond(aSocket:TCustomWinSocket);
    
                function ReadCoils(StartAddress, Count:word):boolean;
                function ReadDiscreteInputs(StartAdd,Count:word):boolean; 
                function ReadHoldingRegisters(RegNo,Count:word):boolean; 
                function ReadInputRegisters(RegNo,Count:word):boolean;
                procedure WriteSingleCoil(Addr:word;value:boolean);
                procedure WriteSingleRegister(RegNo,value:word);
    
                property  DiscreteInput[idx:integer]:boolean read ...
                property  Coils[idx:integer]:boolean read ...
                property  InputRegs[idx:integer]:word read ...
                property  HoldingRegs[idx:integer]:word read ...
    
                property  FC:byte read FFC;
                property  Request:TBytes read GetRequest;
                property  Response:TBytes read GetResponse;
    
                property  OnGetData:TOnExchangeData read ...         
                property  OnSetData:TOnExchangeData read ...
    
               end;
    Νομίζω ότι πλέον γίνεται φανερή η χρησιμότητα των objects.
    Περιλαμβάνουν δεδομένα και μεθόδους που υλοποιούν τις υπηρεσίες τους προς τον χρήστη.

    Έπρεπε, η κλάση TModbus να ανοίγει και την TCP/IP σύνδεση ?
    Αποφάσισα ότι η κλάση θα χρησιμοποιεί connected sockets και θα συνδιαλέγεται με την υπόλοιπη εφαρμογή με καθαρό τρόπο.

    Επιπλέον η μνήμη που γράφει ή διαβάζει ο Server/Slave θα ανήκε στον ίδιο, και την διαχειρίζεται, την εμφανίζει ή στην κλάση TModbus που βασικά χερίζεται μόνο τον σχηματισμό των μηνυμάτων. Επέλεξα η μνήμη να ανήκει στον Slave και να η TModbus κλάση να επικοινωνεί με event Handlers
    Κώδικας:
    TOnExchangeData = procedure(FC:byte; param1,param2:word;var value:word) of object;
    Μπορείτε να κατεβάσετε την εφαρμογή και το source από το
    http://www.filefactory.com/file/vjru...n/Server02.rar

    Παρατηρήσεις και σχόλια πάντα δεκτά.


  3. #23
    Μέλος
    Ημερομηνία εγγραφής
    Mar 2012
    Θέση
    Athens, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    99
    Σειριακή επικοινωνία

    Όταν χρησιμοποιήσαμε PSTN γραμμή συνδέαμε το modem στην σειριακή του υπολογιστή και «βγαίναμε» στο internet.
    Η σειριακή (RS232) επικοινωνία ήταν κυρίαρχη ανάμεσα σε συσκευές που είχαν να ανταλλάξουν δεδομένα. Σήμερα αντικαθίσταται σταδιακά με το USB των 480 kbits.
    Οι 2 σειριακές συσκευές ανταλλάσουν στοιχεία όταν η μία τα ζητήσει από την 2η ή όταν η 2η τα έχει έτοιμα και τα στέλνει χωρίς ερώτηση. Συνήθως οι διάλογοι είναι ερώτηση – απάντηση.
    Η σειριακή επικοινωνία βασίζεται σε ένα ηλεκτρικό και σε ένα λογικό πρωτόκολλο.
    Παλμοί στο καλώδιο
    Πώς θα κωδικοποιήσουμε την πληροφορία ενός byte σε ένα καλώδιο?. Στην σειριακή επικοινωνία δημιουργούμε μια παλμοσειρά σταθερής διάρκειας , με παλμούς για κάθε bit=1 του byte. Προστέθηκαν και κάποιοι επιπλέον παλμοί συγχρονισμού και ανίχνευσης σφάλματος επικοινωνίας και μεταδίδονται στο καλώδιο με συγκεκριμένη ταχύτητα. Χρησιμοποιούμε ένα οποιοδήποτε UART (πχ 8051) με ένα καλώδιο αποστολής (Tx) και ένα καλώδιο λήψης.
    http://www.slideshare.net/pantechsol...municationuart
    Προφανώς, για λόγους συγχρονισμού οι 2 άκρες πρέπει να ρυθμισθούν στα ίδια χαρακτηριστικά σειριακής επικοινωνίας (ταχύτητα, parity, stop bits).

    Ανίχνευση λαθών.
    Κάθε byte περιέχει άρτιο ή περιττό αριθμό από 1. Προσθέτοντας ένα ακόμα bit (παλμό) μπορείς να εξασφαλίσεις ότι ο αριθμός των 1 στο συγκεκριμένο byte είναι άρτιος ή περιττός (parity odd / even). Η ανίχνευση λαθών βασίστηκε στο μέτρημα των 1 κατά την λήψη και την επιβεβαίωση της συμφωνίας επικοινωνίας (odd/even parity).
    Το UART παράγει interrupts όταν ο καταχωρητής αποστολής (transmit register) είναι άδειος και ο καταχωρητής λήψης (receive register) γεμάτος. Αυτά τα interrupts φτάνουν στον driver του σειριακού που τα προωθεί σαν events στις εφαρμογές.
    Έτσι μπορούμε ασύγχρονα να ενημερωθούμε για την λήψη μέσω σειριακής επικοινωνίας ενός χαρακτήρα ή να γράψουμε σε ένα buffer το προς αποστολή μήνυμα και αφήσουμε να αποσταλεί . Όλα αυτά είναι μέρος του λειτουργικού συστήματος και αποτελούν πρόβλημα των οδηγών (drivers) των σειριακών θυρών.
    Τελικά έχουμε 2 συσκευές συνδεδεμένες με τουλάχιστον 3 καλώδια (Tx, Rx, Gnd) όπου το Tx της μίας συσκευής συνδέεται στο Rx της άλλης και έχουν κοινό Gnd.

    Λογικό πρωτόκολλο.
    Ας δούμε ένα πρωτόκολλο επικοινωνίας για φορολογικούς εκτυπωτές (από αυτούς εκδίδονται οι αποδείξεις στα super market).
    ftp://ftp.accordgroup.ro/Pentru%20de...20PROTOCOL.pdf

    Κάθε μήνυμα ξεκινά με τον STX (0x02) και τελειώνει με ΕΤΧ (0x03).
    Κώδικας:
    +-----+---------------+-----+
    | STX |      Data     | ETX |
    +-----+---------------+-----+
    Όλα τα δεδομένα στα Data είναι ascii χαρακτήρες από 0x32 έως 0xFF.

    Tx data packet
    Κώδικας:
    <--------------------- Data -------------------><--Checksum -->
    +---------+---------+---------+------+---------+---------------+
    | Request / Field 1 / Field 2 / .... / Field N /       CC      |
    +---------+---------+---------+------+---------+---------------+
    Request 1 char

    Rx data packet
    Κώδικας:
    <--------------------- Data -----------------------------><-Checksum->
    +---------+--------+--------+-------+-------+------------+-----------+
    |Response /Status 1/Status 2/Field 1/Field 2/..../Field N/    CC     |
    +---------+--------+--------+-------+-------+------------+-----------+
    Response,status1, status2 2 numeric digits

    Κώδικας:
     BYTE CalcChecksum(BYTE *packet)                        
     {                                                      
       BYTE sum = 0;                                        
       int  checklength = strlen(packet) - 2;               
                                                            
       while(check length--) sum += (BYTE) (*packet++);     
       return (sum % 100));                                 
     }
    Σχεδίαση

    1. Κάθε μήνυμα απαντάται σύμφωνα με την παρακάτω ροή

    Sender (host) Receiver (device)
    Κώδικας:
    ---------------------------------------------------------
         IDLE                           IDLE
         ENQUIRE ---------------------> 
                 <--------------------- ACKNOWLEDGE
       Τx PACKET ---------------------> 
                 <--------------------- ACKNOWLEDGE
                 <--------------------- Rx PACKET
     ACKNOWLEDGE --------------------->
            IDLE                        IDLE
    2. Όλα τα μηνύματα αποστολής μορφοποιούνται, πριν αποσταλούν, σε μία ρουτίνα
    3. Όλα τα μηνύματα λαμβάνονται από μία ρουτίνα.
    4. Χρησιμοποιώ blocking διαδικασία επικοινωνίας.

    Η διαδικασία επικοινωνίας περιγράφεται από το παρακάτω διάγραμμα αλλαγής κατάστασης (state transition diagram).

  4. #24
    Μέλος
    Ημερομηνία εγγραφής
    Mar 2012
    Θέση
    Athens, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    99
    και το διάγραμμα.



    Η υλοποίηση ακολουθεί σε επόμενο post.

  5. #25
    Μέλος
    Ημερομηνία εγγραφής
    Mar 2012
    Θέση
    Athens, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    99
    Υλοποίηση

    Σε όλα τα πρωτόκολλα επικοινωνίας υπάρχει ένα μήνυμα αποστολής και ένα μήνυμα απάντησης και πάντα θα υπάρχουν προβλήματα επικοινωνίας. Η υλοποίηση ενός πρωτοκόλλου πρέπει, απαραίτητα, να λαμβάνει υπόψιν τα σφάλματα (επικοινωνίας ή λογικά) και να χειρίζεται ανάλογα. Κρινόμαστε πρώτα από την λειτουργικότητα και έπειτα από την αξιοπιστία κάθε συστήματος. Από επικοινωνίες με σειριακούς φορολογικούς εκτυπωτές έως τις ανεμογεννήτριες.

    TPacket
    Ας ξεκινήσουμε με την πρώτη κλάση που υλοποιεί την λειτουργικότητα του πακέτου αποστολής & λήψης.
    Κώδικας:
    TPacketType = (ptTx, ptRx);
    
    TPacket = class(TObject)
    ...
     public
      constructor Create;            overload;
      constructor Create(aReq:char); overload;
      destructor  destroy; override;
      class function CalcCheckSum(const data: string): string;
      class function ChecksumValid(const data:string):boolean;
      property Fields[index:integer]:string read GetField write SetField;
      property Data:string read GetData write SetData;
      property PacketType:TPacketType read fPacketType write fPacketType;
      property RxResposeCode:integer read fRxResposeCode;
      property RxStatus1:integer  read fRxStatus1;
      property RxStatus2:integer read fRxStatus2;
     end;
    Όταν θέλουμε να δημιουργήσουμε ένα μήνυμα για αποστολή τότε

    Κώδικας:
    tx:=TPacket.Create('b'); // Request = SetVatRates
    for i:=1 to 5 do // προσθέτουμε 5 συντελεστές ΦΠΑ
     tx.Fields[i]:=format('%.2f/',[VatRates[i]]);
    Χρησιμοποιούμε το property data που προσθέτει και checksum

    Όταν λαμβάνουμε ένα μήνυμα
    Κώδικας:
    rx:=TPacket.Create;
    rx.data:=msg; // χωρίς STX, ETX
    τo property RxResposeCode:integer περιέχει την κωδικό επιτυχίας/αποτυχίας.
    Το array property fields τα πεδία του μηνύματος.

    Χρησιμοποίησα για πρώτη φορά class functions. Χρειαζόμουν μία υπηρεσία (θα μπορούσα να είναι μια απλή βοηθητική function) που ανήκε σε μία κλάση και δεν ήθελα να δημιουργήσω object.

    Η κλάση είναι ένα συμβόλαιο που απαριθμεί και περιγράφει υπηρεσίες.
    Για να "πάρουμε" αυτές τις υπηρεσίες δημιουργούμε objects μέσω του constructor κάθε κλάσης. Τα objects καταλαμβάνουν μνήμη για τις εσωτερικές μεταβλητές τους και όλα μοιράζονται τον κώδικα functions.

    Η ίδια η κλάση μπορεί να μας δώσει υπηρεσίες αρκεί να μην χρησιμοποιεί μνήμη του object. O constructor, που δημιουργεί objects αποτελεί ΤΗΝ class function.

    TSession
    Ακολουθεί η κλάση της επικοινωνίας. Εδώ υλοποιήσαμε την λογική του διαγράμματος αλλαγής καταστάσεων.
    Μελετήστε το και καταλάβετέ το. Ο κώδικας είναι πιστή αντιγραφή του.
    Κώδικας:
    TSession = class
    ... 
     public
      constructor Create(const params:string); // COM1,9600,N,8,1
      destructor destroy; override;
      // <0 Σφάλμα επικοινωνίας
      // 0  Επιτυχία επικοινωνίας & εκτέλεσης της εντολής
      // >0 Σφάλμα πρωτοκόλλου
      function  Communicate(const TxPacket:TPacket; 
                            out RxPacket:TPacket):integer;
     end;


    TFiscalPrinter
    Και καταλήγουμε στην κλάση που χρησιμοποιεί η εφαρμογή.
    Αυτή με την σειρά της χρησιμοποιεί την TSession & την Tpacket για την επικοινωνία με τον εκτυπωτή.

    Αλλά οι υπηρεσίες που παρέχει είναι υψηλού επιπέδου, που μπορούν άμεσα να χρησιμοποιηθούν από μία εφαρμογή (application).

    Κώδικας:
    TFiscalPrinter = class
    .. .
     public
      constructor create(const COMPort:string);
      destructor destroy; override;
    
      function FiscalTime:TDateTime;
      function SerialNumber:String;
      function TicketNumber:integer;
      function ZNumber:integer;
    
      procedure OpenDay;        // Logical operation
      procedure CloseDay;       // Issue Z, read EJ
    
      procedure OpenTicket(tt:TTicketType; const Title:string ='';                 
                           const Seira:string=''; number:integer=0);
      procedure SaleItem(const code, descr, barcode:string; 
                         price, quant, amount:double; vatCat:integer);
      procedure VoidItem(const code, descr, barcode:string; 
                         price, quant, amount:double; vatCat:integer);
      procedure RefoundItem(const code, descr, barcode:string; 
                            price, quant, amount:double; vatCat:integer);
      procedure DiscountItem(const code, descr, barcode:string; 
                         amount:double; vatCat:integer);
      procedure SubTotalTicket;
      procedure SubTotalDiscount(amount:double);
    
      procedure CancelTicket;
      procedure CloseTicket;
    ...
     property capEJ:boolean read FcapEJ; // capability has Electronic Journal
     property capSlip:boolean read FcapSlip;// capability has SlipPrinter
    
     property Header[index:integer]:string read GetHeader write SetHeader;
     property Footer[index:integer]:string read GetFooter write SetFooter;
     property VatRates[index:integer]:string read GetVatRate write SetVatRate;    
     end;
    ΥΓ.
    1. Η μόνη μου παρατήρηση στο συγκεκριμένο πρωτόκολλο ήταν η λανθασμένη επιλογή του ‘/’ σαν διαχωριστή πεδίων (field separator) διότι μπορεί να υπάρξει και στα δεδομένα ενός πεδίου του μηνύματος.
    Θα μπορούσε να χρησιμοποιηθεί ο χαρακτήρας FS = 28 ή 0x 1C

    2. Χρησιμοποιώ το async32 component. (open source)
    http://www.general-files.com/downloa...s554ee144h32i0

    3. Με αυτό το post τελείωσα με όσα θέματα είχα σκεφθεί να αναλύσω.
    Αναμένω προτάσεις και ερωτήσεις.

    ΠΦ

  6. #26
    Εξέχον μέλος
    Ημερομηνία εγγραφής
    Jun 2008
    Θέση
    Glyfada, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    3,101
    Τι να πω! Μένω άφωνος!
    Πιστεύω ότι τόση ανάπτυξη κώδικα δεν πρέπει να υπάρχει σε άλλο forum ακόμη και του εξωτερικού.
    Η δουλειά αυτή Πάνο προορίζεται για τους επόμενους αναγνώστες του forum αυτού που αφενός θα είναι ειδικοί για να μπορέσουν να παρακολουθήσουν όλο αυτό το ξεδίπλωμα και αφετέρου πολύ ειδικοί θα έλεγα!

    Εγώ δεν έχω να παρατηρήσω τίποτα διότι δεν έχω ασχοληθεί με modbus και επικοινωνίες γενικώς. Αν χρειασθεί να ασχοληθώ ξέρω αμέσως που θα ανατρέξω μιας και το έχω ήδη αρχειοθετήσει σε 23 σελίδες του acrobat.
    Το μόνο που θα πω είναι ότι αυτή η καταπληκτική γλώσσα (Pascal) που την πεθάνανε οι πεθαμεναντζήδες πάντα με συγκινεί!! Ακόμα και όταν την γράφει άλλος και όχι εγώ!


    Υ.Σ.
    Μη περιμένεις «λαοθάλασσα» να σε ρωτάει και συ να απαντάς. Χτύπησες με την μία πολύ ψηλά τόσο ψηλά που θα πάσχεις από «μοναξιά». Έχε το υπ' όψη σου αυτό. Και πάλι εύγε!


    Στίγμα Μαστροκαπετάνιου

  7. #27
    Μέλος
    Ημερομηνία εγγραφής
    Mar 2012
    Θέση
    Athens, , Greece.
    Ιδιότητα:
    Αγνωστη
    Απαντήσεις
    99

    Ηλεκτρονικό κατάστημα ειδών θέρμανσης και κλιματισμού

    Καπετάνιε,

    Από την αρχή σκόπευα στους νέους μηχανικούς. Αυτούς που στο πολυτεχνείο διδάχθηκαν να λύνουν προβλήματα. Σε αυτούς που τα μαθηματικά ήταν εργαλείο και όχι αυτοσκοπός. Μάθαμε και μαθαίνουνε να λύνουν προβλήματα αναλυτικά (σπάζοντάς τα σε μικρότερα κομμάτια) και συνδυαστικά (να ενώνουν μικρά κομμάτια γνωστής συμπεριφοράς και να δημιουργούν μεγαλύτερα).

    Αλλά το λογισμικό, όσο soft και να είναι, απαιτεί σχεδίαση πριν την υλοποίηση.
    Αυτό προσπάθησα να τονίσω. Και πλέον οι κλάσεις και τα objects αποτελούν εργαλεία για να γράφουμε καλύτερο λογισμικό.
    quote:πολύ ειδικοί θα έλεγα!
    Δεν συμφωνώ για το πολύ ειδικοί. Αν οι μηχανολόγοι και οι ηλεκτρολόγοι πρέπει να γνωρίζουν τους τρόπους μετάδοσης της θερμότητας τότε θα πρέπει να γνωρίζουν να γράφουν και σωστά λογισμικό. Οι μηχανικοί φτιάχνουν συστήματα και όχι οι προγραμματιστές. Η γλώσσα προγραμματισμού αλλάζει αλλά όπως είπε και ο Σωτήρης
    quote:Οι τεχνικες παραμενουν τεχνικες..
    Εσύ ήσουν ήδη έτοιμος. Η περιέργειά σου σε είχε ήδη οδηγήσει στο να εξερευνήσεις τον αντικειμενοστρεφή προγραμματισμό. Αλλά να αλλάξεις αυτή την τεράστια unit με τις βοηθητικές functions που ξέρεις πια απέξω είναι δύσκολο.

    Που ξέρεις, μπορεί τελικά και να προσελκύσω νέα μέλη μόνο για το λογισμικό. Αυτοί φυσικά θα μετράνε στους Ηλεκτρολόγους.

    Θα συνεχίσω να γράφω για διάφορα θέματα που θα επιλέγω με το ίδιο, χαλαρό, ρυθμό.

    Δάσκαλε, σε ευχαριστώ θερμά.
    Πάνος

Δικαιώματα απάντησης

  • You may not post new threads
  • ΔΕΝ έχετε το δικαίωμα απάντησης
  • You may not post attachments
  • ΔΕΝ μπορείτε να επεξεργαστειτε τις απαντησεις σας
  •  
  • BB code is Ανοικτό
  • Smilies are Ανοικτό
  • [IMG] code is Ανοικτό
  • [VIDEO] code is Κλειστό
  • HTML code is Κλειστό