Algorytm obliczania bajtu kontrolnego dla drukarki Posnet Thermal

0

Dzień dobry.
Jestem w posiadaniu drukarki posnet thermal. Aby coś wydrukować należy wysłać po RS-ie odpowiednie dane. Cały problem w tym, że wymagane jest coś takiego jak bajt kontrolny, a ja za nic nie wiem jak go obliczyć. Znalazłem instrukcję programisty w której pisze:

Większość sekwencji jest uzupełniona dwoma znakami ('cc') - cyframi HEX (znaki ze zbioru: '0'..'9', 'A'..'F'), które wyrażają w zapisie heksadecymalnym wartość bajtu kontrolnego liczonego dla całej sekwencji w specjalny sposób:

  1. Na początku podstawiamy wartość bajtu kontrolnego:
    <byte> := 255;

  2. Począwszy od następnego znaku za ESC P obliczamy wartość wyrażenia:
    <byte> := <byte> xor <kod danego="danego" znaku="znaku">;
    aż do ostatniego znaku sekwencji (nie licząc znaków 'cc' i "końcówki" ESC ).

  3. Tak uzyskany bajt kontrolny wyrażamy postaci w cyfr heksadecymalnych i dopisujemy do sekwencji wraz z końcówką 'ESC '.

UWAGA:

Oznaczenie "xor" użyte w procedurze oznacza funkcję logiczną Exclusive Or czyli: ”lub wykluczające”, inaczej zwane "albo". Funkcja ta działa na pojedynczych bitach bajtu. Dla bitów o różnych wartościach wynosi 1, a dla takich samych - 0. Jeśli w użytkowanym przez Państwa języku programowania brakuje funkcji xor, to można ją zdefiniować następująco:

xor (a,b) := (( not a) and b) or (( not b) and a).

Oczywiście użycie nawiasów nie jest tu konieczne ze względu na przestrzeganą chyba we wszystkich językach siłę wiązania poszczególnych funkcji.

Cyfry kontrolne obliczane w ten sposób dają dobre zabezpieczenie przed przekłamaniami transmisji. Przykładowa procedura dopisująca te znaki, napisana w języku PASCAL jest pokazana w rozdziale „Ogólne zasady syntaktyki komend.”

Oraz ten właśnie przykład:

cc - bajt kontrolny, zakodowany jako 2 cyfry HEX (EXOR wszystkich znaków za ESC P do tego bajtu z wartością początkową = #255), dokładnie według następującego algorytmu w języku PASCAL:

begin
  check := 255;

  for i:= 3 to length(sekwencja)-4 do
    check := check xor byte(sekwencja[i]);
end;

Z powyższego wynika, że do kontroli sekwencji nie bierzemy 2 pierwszych znaków (ESC P) oraz 4-ch ostatnich (2 znaki kodujące bajt kontrolny oraz terminator sekwencji ESC ),

UWAGA :

  1. Obliczoną w powyższej pętli liczbę check stanowiącą wartość bajtu kontrolnego należy przekodować na 2 znaki HEX (znaki ASCII ze zbioru: '0'..'9','A'..'F','a'..'f'), np. korzystając z poniższej funkcji byte_to_hex:
{ *** konwersja liczby na string 'hex' *** }
function word_to_hex (w: word) : string;
var
  i : integer;
  s : string;
  b : byte;
  c : char;
begin
  s := '';

  for i:= 0 to 3 do
  begin
    b := (hi(w) shr 4) and 15;
    case b of
      0..9 : c := char(b+$30);
      10..15 : c := char(b+$41-10);
    end;
    s := s + c;
    w := w shl 4;
  end;
  word_ to_hex := s;
end;

{***  konwersja bajtu na 2 znaki ***}
function byte_to_hex (b:byte) : string;
begin
  byte_to_hex := copy(word_to_hex(word(b)),3,2);
end;

Niestety mimo pewnych prób nic z tego nie wychodzi. Jestem raczej elektronikiem niż informatykiem, ale musiałem się tego podjąć.
Wiadomo np, że dobrą sekwencją jest:

&1BP1;0$lMoje&0D1&0DA/0.01/0.01/0/E0&1B\
Bajtem kontrolnym jest tu E0

&1B to znak ESC a &0D to Carriage Return (CR). Wiem też również, że liczby 0.01 nie biorą raczej udziału w tym algorytmie, ponieważ gdy je zmieniam to drukarki nie wyrzuca żadnego błędu i drukuje bez problemu.

Ma ktoś może jakiś pomysł jak powinien wyglądać taki algorytm?

0

Nic nie piszesz o języku. Mam działający kod napisany w Javie. Może być?

2

Działający przykład z pascala:
Tworzenie pojedyńćzej komendy:

Result := #27 + 'P' + Data + CalcCRC(Data) + #27 + '\';

Jak widzisz na początku jest bajt o wartości 27 (1b hex) potem jest duże P wszystko to co występuje po P to dane, z których jest wyliczane CRC i na końcu znowu bajt o wartości 27 i znak \

A tak jest wyliczane CRC

function CalcCRC(Data: string): string;
var
  CRC: byte;
  i: integer;
begin
  CRC := 255;
  for i := 1 to Length(Data) do
    CRC := CRC xor byte(Data[i]);
  Result := IntToHex(CRC, 2);
end;
0
bogdans napisał(a):

Nic nie piszesz o języku. Mam działający kod napisany w Javie. Może być?

Na razie wszystko do drukarki wysyłam ręcznie, docelowy program będę pisał prawdopodobnie w Delphi. Kod napisany w Javie może być, zobaczę jak to realizujesz i spróbuję przenieść na inny język.
Problemem jest dla mnie znak CT (0D), ponieważ jak by nie robił nie wiem jak go poprawnie uwzględnić. Mam napisany prawie działający kod w Pascalu z tym, że jeśli w sekwencji jakieś słowo się powtarza (w tej z pierwszego postu właśnie &0D) to nie wpływa ono na sumę kontrolną. Tak więc program sumę liczy mi poprawnie. Jednak już np. sekwencja kończąca wydruk posiada tylko jedno CR i nie umie mi poprawnie wyliczyć sumy. Jak policzyłem na piechotę na bajtach to wyszła ok (wstawiając 13 za 0D).

Co do postu abrakadaber to nie za bardzo to rozumiem :(

1

czego nie rozumiesz?
Chcesz wysłać do drukarki polecenie 1;0$lMoje&0D1&0DA/0.01/0.01/0/ - to z Twojego postu
to w tym kodzie
Result := #27 + 'P' + Data + CalcCRC(Data) + #27 + '\';
zamiast Data wstawiasz ten ciąg 1;0$lMoje&0D1&0DA/0.01/0.01/0/ i w Result masz GOTOWY tekst, który należy wysłać do drukarki

0

Ten kod nie za bardzo chce mi się kompilować. Musze dodać, że korzystam z czegoś takiego jak kompilator online (http://www.compileonline.com/compile_pascal_online.php). Ale ogólnie Twój kod naprowadził mnie na to, że te nieszczęsne CR muszę podać jako dziesiętne 13, a wtedy suma kontrolna się zgadza.

0
  1. chodzi Ci o &0D1&0DA w tym przykładzie 1;0$lMoje&0D1&0DA/0.01/0.01/0/? Jeśli to są znaki CRLF to powinno być 1;0$lMoje' + #13 + '1' + #13 + 'A/0.01/0.01/0/
  2. to moje to nie jest gotowy kod więc nie wiem jak to chcesz kompilować

POPRAWIONA BŁĘDNA INTERPRETACJA MRÓWKÓW NA MONITORZE

0

Że też @abrakadaber doszedł o co chodzi @Znawca tematu. Po pierwsze @Znawca tematu podajemy sensowne tagi. Nic nam nie mówi alogrytm. Ważne jest od razu określenie języka. Poprawiłem to. Natomiast to co napisal @abrakadaber dopiero ma sens. Bo Ty podajesz jako dane coś z &1B.

Także zachodzilem w głowę jakim cudem zliczanie może być od 3ego znaku. A za pewne te dane są jako ASCII. Wtedy by się zgadzało. Na szczęście rozgryzł to @abrakadaber (plus za to), bo ja nie mam żadnego doświadczenia z tymi drukarkami. Zatwierdź więc jego odpowiedź. Poza tym logiczne, że CR = 13. Tutaj Ameryka nie została odkryta.

EDIT: @Znawca tematu zatwierdziłem rozwiązanie @abrakadaber. A Ty masz takiego nicka, że motać się nie masz wręcz prawa. Z czym masz jeszcze problem? Kod z pod Delphi 7 - zwraca E0 jako CRC? Zwraca. No to cytując klasyka "dziękuję, dobranoc" ;)

program test;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function CalcCRC(Data: string): string;
var
  CRC: byte;
  i: integer;
begin
  CRC := 255;
  for i := 1 to Length(Data) do
    CRC := CRC xor byte(Data[i]);
  Result := IntToHex(CRC, 2);
end;

var
  Data : string;
begin
  Data := '1;0$lMoje&0D1&0DA/0.01/0.01/0/';
  Writeln(#27 + 'P' + Data + CalcCRC(Data) + #27 + '\');
end.
0
abrakadaber napisał(a):
  1. chodzi Ci o &0D1&0DA w tym przykładzie 1;0$lMoje&0D1&0DA/0.01/0.01/0/? Jeśli to są znaki CRLF to powinno być 1;0$lMoje' + #13#10 + '0.01/0.01/0/
  2. to moje to nie jest gotowy kod więc nie wiem jak to chcesz kompilować

ad.1 No właśnie chodzi o to, że &0D1&0DA poprawnie ma być rozpatrzone jako &0D 1 &0D A, a nie jako całość czyli pisząc inaczej:'1;0$lMoje' + #13 + '1' + #13 + 'A/0.01/0.01/0/'
Pozostaje tylko to jakoś "zautomatyzować" żeby nie klepać tego ręcznie z klawiatury zależnie od ilości znaków CR (#13) w sekwencji.
ad.2 Tak, wiem że to nie jest gotowy, dopisywałem resztę, ale jakoś nie szło :(

0

@Znawca tematu: rozwiązanie @abrakadaber, jakie zatwierdziłem po pierwsze działa, po drugie zwraca właściwe CRC dla podanych przez Ciebie danych. Także jak domniemuje problemu nie ma.

0

Gdy zaczynałem pisać poprzednią odpowiedz nie widziałem jeszcze Twojego postu.
@olesio mógłbyś jeszcze sprawdzić co otrzymujesz jeśli sekwencja wynosi 1;0$e21S&0D28.29/28.29/ (bo nim zainstaluje delphi i trochę minie), a to ostatecznie odpowie mi czy działa poprawnie.

0

Jak już wspomniałem zajmuję się na co dzień raczej elektroniką niż informatyką. Dziękuję za stron, bo ta na pewno mi się przyda. Mam niestety złą informacje, program nie działa poprawnie. Otrzymuję 86 a poprawnie jest D9. To jest właśnie to o czym wspominałem. Jeśli w sekwencji występuje dwa razy to samo to nie wpływa to na sumę kontrolną (sekwencja z pierwszego postu). Natomiast ta ostatnia sekwencja zawiera tylko jeden znak CR (czyli w hex 0D ASCII) (fakt, na początku namieszałem że nie napisałem dokładnie iż o to mi chodzi, że znaki są z ASCII).

Myślę, że z tym sobie jakoś dam radę . Chyba, że komuś się chce podrzucić pomysł jak to najprościej rozwiązać :)

1
protokół Posnet napisał(a)

[LBTREXIT]: Standardowe zatwierdzenie transakcji
3 warianty 'pozytywnego' zatwierdzenia transakcji:
ESC P 1 ; Pr $e <kod> CR WPLATA / TOTAL / <check> ESC
lub :
ESC P 1; Pr; Pn; Pc $e <kod> CR <linia1> CR <linia2> CR <linia3> CR
WPŁATA / TOTAL / <check> ESC
lub:
ESC P 1; Pr; Pn; Pc; Px; Py $e <kod> CR <linia1> CR <linia2> CR <linia3> CR
WPLATA / TOTAL / RABAT / <check> ESC
Gdzie:
Pr = 0...99 : opcjonalny rabat procentowy dla całej transakcji,
Pn = ilość dodatkowych linii umieszczanych w stopce paragonu, za logo
fiskalnym, do których ma dostęp aplikacja = 0..3,
28
Pc = 0 : zachowanie 'dotychczasowe' tzn. zakończenie drukowania, wysunięcie
papieru i zakończenie trybu transakcyjnego,
= 1 : j.w.,
= 2 : wydrukowanie nagłówka nowego paragonu.
Px: rodzaj rabatu (niezależny od parametru Pr)
= 0 brak,
= 1 rabat procentowy,
= 2 narzut procentowy,
= 3 rabat kwotowy,
= 4 narzut kwotowy,
Py= 1 : parametr ignorowany,
<linia1>...<linia3>: string[40] zawartość linii dodatkowych. Między dwoma
raportami dobowymi można przesłać 25 różnych linii. Wszystkie przesłane
linie drukują się po raporcie dobowym. Za pomocą linii dodatkowych może
być przesyłany numer systemowy. Opis w rozdziale 6.1.
<kod>: string[3], kod kasy (1 znak) + kod kasjera (2 znaki), pole jest
ignorowane, jeżeli wcześniej było logowanie kasjera (sekwencja LBLOGIN)
WPŁATA: (przyjmuje wartości takie jak pole CENA w sekwencji $l) kwota wpłacona
przez klienta, jeżeli = 0 to napisy "wpłata / reszta" nie będą drukowane.
TOTAL: łączna należność dla klienta, dla kontroli poprzednich transferów,
RABAT: rabat/narzut procentowy/kwotowy dla całej transakcji, jeżeli parametr
Px<>0. W przypadku rabatu/narzutu kwotowego wpisuje się kwotę o którą
zmieniana jest należność, w przypadku rabatu/narzutu procentowego wpisuje
się procent o jaki zmieniana jest należność.
<check>: bajt kontrolny, jw.

czyli to co tam podajesz to

1;0$e21S&0D28.29/28.29/

1; - stała
0 - Pr = 0...99 : opcjonalny rabat procentowy dla całej transakcji,
$e - stała
21S - <kod>: string[3], kod kasy (1 znak) + kod kasjera (2 znaki)
&0D - powinien być JEDEN znak o kodzie 13 (0D szesnastkowo) a nie TRZY znaki!
28.29 - WPŁATA: (przyjmuje wartości takie jak pole CENA w sekwencji $l) kwota wpłacona przez klienta, jeżeli = 0 to napisy "wpłata / reszta" nie będą drukowane.
/ - stała
28.29 - TOTAL: łączna należność dla klienta, dla kontroli poprzednich transferów,
/ - stała

prawidłowo ten ciąg w Pascalu powinien wyglądać tak:

s := '1;0$e21S' + #13 + '28.29/28.29/';

i jak tak to wpiszesz to masz prawidłowy wynik: http://ideone.com/tMjhBc czyli CRC=D9

0
abrakadaber napisał(a):

&0D - powinien być JEDEN znak o kodzie 13 (0D szesnastkowo) a nie TRZY znaki!

I właśnie cały czas próbowałem to przekazać, że ma wysłać jako jeden znak.

abrakadaber napisał(a):

prawidłowo ten ciąg w Pascalu powinien wyglądać tak:

s := '1;0$e21S' + #13 + '28.29/28.29/';

Tak, wiem że powinien wyglądać tak co już wcześniej pisałem, jednak teraz muszę to jakoś zautomatyzować, ponieważ te sekwencje się cały czas zmieniają, raz występuje w niej dwa razy '&0D' (oczywiście jako jeden znak), a raz dwa razy a nie będę przecież ręcznie wpisywał w każdą '#13'

0
s := StringReplace(s, '&0D', #13, [rfReplaceAll]);

co daje np http://ideone.com/tMjhBc
i muszę Ci powiedzieć, że masz problem z przekazaniem tego co chcesz zrobić :). Bo podejrzewam, że dane z których robisz paragon bierzesz z jakiegoś pliku czy czegoś podobnego, prawda?

0

Dziękuję Wam za pomoc, programik w delphi do obsługi drukarki już działa.
Pozdrawiam

0

Witam serdecznie,
Udało Ci się zrobić ten programik do wydruku paragonów na posnecie?:)

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