Little endian to Big endian (in Perl).

 c0de  Comments Off on Little endian to Big endian (in Perl).
Aug 232011
 

It is one of the problems some network programmers meet while coding. While debugging executable files You may have noticed that, when seen in hexadecimal editor, some data in the files is ordered backwards. First you see the 1 and then several zeroes. But you know for sure, that the value you encoded for a constant is actually “1” instead of 01 00 00 00.This is what Little endian byte order actually is.Have a look in the decimal value 305,419,896 in HEX. It is 0x12345678. If your machine is Big endian, you will see this value stored in a binary file looking the same way it is. But if your machine is little endian, The value stored in the file will look like 78 56 34 12. The least significant byte will be in front, and the most significant will be in back.

The reason for this goes back in the computer’s past, when it was more important to make MATH really fast in spite of easiness. Imagine you have 1 physical address. This address has the value 01 00 00 00. If you need to read this address as byte, word or long integer. You will ALWAYS get the value 1, which is the real value. If you are using Big endian byte order, you will need to get the address + 4 if you need byte, address + 2 if you need word. The little endian byte order is still used in network equipment and serial interface dialog, so it’s good to know how to cope with it… or you will get really annoyed 😉

01 00 00 00 (byte) = 1
01 00 00 00 (word) = 1
01 00 00 00 (long) = 1

Also, there is the need for speed. If your processor has only 3 real registers (X, Y and Accumulator), than it’s easier to compute the values if they are little endian.

Y = 0;
A = 1;
foreach byte do {
    X = readbyte;               # we read next byte
    if X > 0 do {               # Check if the computation is needed at all.
        Y = Y + (X * A);        # we use Y to collect the results of all bytes multiplied by A
    }
    SHL A << 8;                 # A grows from 1 to 256 to 65536 to 16777216 (2^0, 2^8, 2^16, 2^24)
}

This is of course an ideal case of what is now known as 64 bit processor. One that can use 64 bit words and registers. In case you are using Big endian byte order you cannot use the multiplication but integer division, which is slower.

If you are Linux user but unsure what endianness your equipment uses, execute this:

bash-4.1# perl -MConfig -e 'print "$Config{byteorder}\n";'

If you want this byte order reversed and shown using simple math, use this Perl script:

#!/usr/bin/perl -w

my $a1;
my $a2;
my $a3;
my $a4;
my $r;

if (! $ARGV[0]) {die "There are no args";}

$r = $ARGV[0];

$a1 = $r % 256;
$a2 = ($r - $a1) % 65536;
$a3 = ($r - $a1 - $a2) % 16777216;
$a4 = ($r - $a1 - $a2 - $a3);

$a2 = $a2 / 256;
$a3 = $a3 / 65536;
$a4 = $a4 / 16777216;

printf ("\n$r is equal to 0x%02x 0x%02x 0x%02x 0x%02x\n", $a4, $a3, $a2, $a1);
printf ("$r is reverted to 0x%02x 0x%02x 0x%02x 0x%02x\n\n", $a1, $a2, $a3, $a4);

This is just a rough example. There is easier way to make and explain it. Get the input value, the least significant byte can be obtained by getting the "modulo" of integer division from the input value, store this modulo in an array. After this subtract the modulo from this input value and divide the value by 256. This is the new input value. Repeat, until value is zero. Voila. You have an array of [most significant, less significant ... least significant] bytes.

The script will only work on Long integers, but can easily be made to work with 64 bit words. Made It short, because it's more easy to read. With Little endian, we will always see backward byte order:

bash-4.1# ./RevertByteOrder.pl 305419896

305419896 is equal to    0x12 0x34 0x56 0x78
305419896 is reverted to 0x78 0x56 0x34 0x12

bash-4.1# ./RevertByteOrder.pl 22136

22136 is equal to    0x00 0x00 0x56 0x78
22136 is reverted to 0x78 0x56 0x00 0x00

bash-4.1# ./RevertByteOrder.pl 4660

4660 is equal to    0x00 0x00 0x12 0x34
4660 is reverted to 0x34 0x12 0x00 0x00

bash-4.1#

And if you need to see how many bytes per integer and long integer is your equipment using, execute this:

bash-4.1# perl -V:{short,int,long{,long}}

I hope this explains the Little and Big endian byte order. You can check the articles I've used for more information.

* http://stackoverflow.com/questions/2610849/finding-if-the-system-is-little-endian-or-big-endian-with-perl

* http://docstore.mik.ua/orelly/perl/prog3/ch25_02.htm

 Posted by at 12:07 pm
Aug 152011
 

Datecs Fiscal printers FP 3530/3550 are also known as Citizen IDP 3530/3550, so if you have that printer instead of Datecs version – this little neat program will also work for you.

I wrote this code for a Cable TV operator in the city of Sofia (Bulgaria) long ago. It is closed now, but this code was running on 50 of their cash desks (all running Slackware Linux). I did some modifications but they are lost and not retrievable anymore. Most of this is tested on 3530. 3550 is also having paper feed cutter, this code cannot cut the paper, but It can be added quick if anyone can provide a FP3550 unit for testing.

The code is a bit old and is my first Perl script, a bit chunky and raw and also I wrote Perl at this days inspired by Pascal, so if you think you can better the code – feel free to post suggestions and links. I will appreciate them.

Basically, you need to set a symlink to your printer in /dev/ or change the line pointing to the device to /dev/ttyS0 or wherever your Fiscal printer resides on the machine.

#!/usr/bin/perl -w
#
use strict;
use warnings;
use Device::SerialPort ;

# Globals

my $last_command = 0;
my $last_index = 0;
my $verbose = 0;
my $port = "/dev/FP3530"; 	# You need to set this manually with symlink to the serial port where, 
				# the fp3530 is set if you are not executinh thse script as root.
				# if you are root, you can change this line to your actual serial port.

sub encode {
# Function encode (sequence, command, data)
# sequence is between 0x20 and 0x7f
# command is described in the printer's manual
# data depends on command and can be OMITED
# encoded message must be <01><"DATA"><05><03>
	my $seq = shift;
	my $cmd = shift;
	my $data = shift;
# Cyrilize old style if needed :( the printer has Cyrilic starting from 0x80 ... must be 0xc0 to show properly
	for (my $i = 0; $i < length($data); $i++ ) {
		if (ord(substr($data,$i,1)) > 0xbf) { substr($data,$i,1) = chr(ord(substr($data,$i,1))-0x40);}
	}
	my $bcc = 0;						#bcc is the sum of all bytes after #01 including 0x05
	$bcc += ord($seq);
	$bcc += ord($cmd);
	for (my $i = 0; $i < length($data) ; $i++) {
		$bcc += ord(substr($data, $i, 1));
	}
	$bcc += 0x05;						# 0x05 is data terminator
	my $lng = 32 + 4 + length($data);			# second byte from the message is the length of 
	$bcc += $lng;						# bytes between 0x01(excl) and 0x05(incl) plus 0x20
	my $b1 = chr(0x30 + ($bcc - ($bcc % 4096)) / 4096);	# after calculating BCC must be *encoded*
	my $hlp = $bcc % 4096;					# if Checksum is 0x1234 then BCC must be 0x31 0x32 0x33 0x34
	my $b2 = chr(0x30 + ($hlp - ($hlp % 256)) / 256);
	$hlp = $bcc % 256;
	my $b3 = chr(0x30 + ($hlp - ($hlp % 16)) / 16);
	$hlp = $bcc % 16;
	my $b4 = chr(0x30 + $hlp);
	my $len = chr($lng);
	my $encoded = chr(0x01) . $len . $seq . $cmd . $data . chr(0x05) . $b1 . $b2 . $b3 . $b4 . chr(0x03) ; # <-- completed message
# Debuging
	if ($verbose) {
		my $encoded_textual = "";
		for (my $i = 0; $i < length($encoded); $i++) {
			$encoded_textual = $encoded_textual . sprintf("%02x", ord(substr($encoded,$i,1))) . "";
		}
		printf ("Encoded message : $encoded_textual\n");
	}
# end of Debuging 
	return ($encoded);
}

sub prn_reply {
	my $msg = shift;
	$last_command = ord(substr($msg,3,1));
	$last_index = ord(substr($msg,2,1));
	if ($verbose) {
		if ($msg ne "") {
			print ("Prn replied with: ");
			for (my $i = 0; $i < length($msg); $i++) {
				my $tmp = sprintf("%02x", ord(substr($msg, $i, 1)));
				if ($tmp ne "16") {
					print ($tmp . "");
				}
			}
		}
		print ("($msg)\n");

		print ("Command was : $last_command\n Index was: $last_index\n");
	}
# strip the 6 status bytes from reply message
	my $flag = "";
	my @sb;
	my $j = 0;
	for (my $i = 0; $i < length($msg); $i++) {
		my $tmp = ord(substr($msg, $i, 1));
		if ($tmp == 0x05) { $flag = "0"}
		if ($flag) {
			$sb[$j] = sprintf("%08b", $tmp);
			if ($verbose) {print("Status bits $j are ".$sb[$j],"\n");}
			$j++;
		}
		if ($tmp == 0x04) { $flag = "1"}
	}
# FATAL ERRORS
	if (substr($sb[0],2,1) eq "1") { print("Fatal Error :\n"); }
	if (substr($sb[0],3,1) eq "1") { print(" Mechanics Error !\n"); }
	if (substr($sb[0],6,1) eq "1") { print(" Invalid OPcode !\n"); }
	if (substr($sb[0],7,1) eq "1") { print(" DATA Syntax error !\n"); }
	if (substr($sb[1],3,1) eq "1") { print(" MEMORY CORRUPT !\n"); }
	if (substr($sb[1],4,1) eq "1") { print(" Print Canceled !\n"); }
	if (substr($sb[1],5,1) eq "1") { print(" MEMORY Cleared !\n"); }
	if (substr($sb[1],6,1) eq "1") { print(" Command not allowed in current fiscal mode !\n"); }
	if (substr($sb[2],7,1) eq "1") { print(" NO PAPER !\n"); }
	if (substr($sb[4],2,1) eq "1") { print("MEMORY ERROR :\n"); }
	if (substr($sb[4],3,1) eq "1") { print(" Memory FULL !\n"); }
	if (substr($sb[5],5,1) eq "1") { print(" Unknown memory error !\n"); }
	if (substr($sb[5],7,1) eq "1") { print(" Memory is READONLY !\n"); }
# Misc and nonFatal Errors 
	if ($verbose) {
		if (substr($sb[0],4,1) eq "1") { print("Display pluged\n"); } else {print("No display plugged!\n");}
		if (substr($sb[0],5,1) eq "1") { print("Clock is not set!\n"); } else {print("Clock is set\n");}
		if (substr($sb[1],7,1) eq "1") { print("Cash Overflow ! Reduce !\n"); }
		if (substr($sb[2],2,1) eq "1") { print("Nonfiscal BON opened !\n"); }
		if (substr($sb[2],4,1) eq "1") { print("Fiscal BON opened !\n"); }
		if (substr($sb[3],1,1) eq "1") { print("Switch 2.2 is ON\n"); } else {print("Switch 2.2 is OFF\n");}
		if (substr($sb[3],2,1) eq "1") { print("Switch 2.1 is ON\n"); } else {print("Switch 2.2 is OFF\n");}
		if (substr($sb[3],3,1) eq "1") { print("Switch 1.5 is ON\n"); } else {print("Switch 1.5 is OFF\n");}
		if (substr($sb[3],4,1) eq "1") { print("Switch 1.4 is ON\n"); } else {print("Switch 1.4 is OFF\n");}
		if (substr($sb[3],5,1) eq "1") { print("Switch 1.3 is ON\n"); } else {print("Switch 1.3 is OFF\n");}
		if (substr($sb[3],6,1) eq "1") { print("Switch 1.2 is ON\n"); } else {print("Switch 1.2 is OFF\n");}
		if (substr($sb[3],7,1) eq "1") { print("Switch 1.1 is ON\n"); } else {print("Switch 1.1 is OFF\n");}
		if (substr($sb[4],4,1) eq "1") { print("Less than 50 units of memory remain !\n"); }
		if (substr($sb[4],5,1) eq "1") { print("NO FISCAL MEMORY !\n"); }
		if (substr($sb[4],7,1) eq "1") { print("Memory write error !\n"); }
		if (substr($sb[5],2,1) eq "1") { print("Memory and fiscal data set !\n"); } else { print("Memory and fiscal data are NOT set !\n"); }
		if (substr($sb[5],3,1) eq "1") { print("Tax groups set !\n"); } else { print("Tax groups are NOT set !\n"); }
		if (substr($sb[5],4,1) eq "1") { print("Printer is in fiscal mode !\n"); } else { print("Printer is NOT in fiscal mode !\n"); }
		if (substr($sb[5],6,1) eq "1") { print("Fiscal memory is cleared !\n"); }
	}
}

$#ARGV > -1 || die "\n...some arguments missing !\n\nusage :\n\nfiscalprn.pl [-v] command [data]\n\nExample:\nfiscalprn.pl -v setclock \"DD-MM-YY HH:MM:SS\"\nfiscalprn.pl -v openfiscal\nfiscalprn.pl -v add \"Apples\" \"A\" \"10.00\"\nfiscalprn.pl -v add \"Pears\" \"B\" \"10.00\"\nfiscalprn.pl -v total\nfiscalprn.pl -v closefiscal\nfiscalprn.pl -v newline\n\n";
# ok, all there, now let's interpret it !
my @cmdline = @ARGV;
if ($cmdline[0] eq "-v") {
	$verbose = 1;
	shift @cmdline;
	if ($#cmdline == -1) {die "\nWhat to verbose ?\n"};
}

my $command = shift(@cmdline);

# Initialize the fiscal printer
my $prn = Device::SerialPort->new ($port) || die "Cannot open $port !";
$prn->baudrate(9600);
$prn->parity("none");
$prn->databits(8);
$prn->stopbits(1);
$prn->handshake("none");
$prn->write_settings || die "Cannot setup printer !?";

my $enccmd = &encode (chr(0x20), chr(0x4a), "");
my $pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
sleep 1;
&prn_reply($prn->input);

if ($command eq "openfiscal") {
# data format is ,,[,]
# where Operator, password & OperatorPlace are preset index
# and I means that current Bon is treated as Invoice
	$enccmd = &encode (chr(0x21), chr(0x30), "1,0000,1");
	$pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
	sleep 1;
	&prn_reply($prn->input);
}

if ($command eq "newline") {
# data format []
	$enccmd = &encode (chr(0x21), chr(0x2c), shift(@cmdline));
	$pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
	sleep 1;
	&prn_reply($prn->input);
}

if ($command eq "add") {
# data format [][]<[sign]price>[*][,perc]
# text1 and text2 are both limited to 25 chars. 
	if ($#cmdline != 2) {die "\nNot enough arguments for invoice line !\n\n format is \n"}
	$enccmd = &encode (chr(0x21), chr(0x31), shift(@cmdline).chr(0x09).shift(@cmdline).shift(@cmdline));
	$pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
	sleep 1;
	&prn_reply($prn->input);
}

if ($command eq "total") {
# data format is [][][[Paidmode]<[sign]amount>]
	$enccmd = &encode (chr(0x21), chr(0x35), chr(0x09));
	$pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
	sleep 1;
	&prn_reply($prn->input);
}

if ($command eq "closefiscal") {
# no data
	$enccmd = &encode (chr(0x21), chr(0x38), "");
	$pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
	sleep 1;
	&prn_reply($prn->input);
}

if ($command eq "setclock") {
# no data
	$enccmd = &encode (chr(0x21), chr(0x3d), shift(@cmdline));
	$pass = $prn->write($enccmd) || die "Cannot communicate with Fiscal printer !";
	sleep 1;
	&prn_reply($prn->input);
}

# Close printer handle
sleep 1;
undef $prn;

__END__

=head1 NAME

fiscalprn - a CLI program for printing invoices with Datecs FP3530

=head1 SYNOPSIS

usage :

fiscalprn.pl [-v] command [data]

Example:
fiscalprn.pl -v setclock "DD-MM-YY HH:MM:SS"
fiscalprn.pl -v openfiscal
fiscalprn.pl -v add "Apples" "B" "10.00"
fiscalprn.pl -v add "Pears" "B" "10.00"
fiscalprn.pl -v total
fiscalprn.pl -v closefiscal
fiscalprn.pl -v newline

=head1 DESCRIPTION

fiscalprn.pl - a CLI program for printing invoices with Datecs FP3530

=head1 SEE ALSO

Need a symlink to your serial port named /dev/FP3530 and the Perl module Device::SerialPort

=head1 AUTHOR

 Stoill Barzakov

=cut
 Posted by at 2:07 pm