package Protocol;
use warnings;
use strict;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT = qw( modbus_crc16 $protocol read_parameter_frame );

use Data::Dump qw(dump);

my $debug = $ENV{DEBUG} || 0;

# Table of CRC values for high order byte
use constant CRC_HI => [
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40
];
 
# Table of CRC values for low order byte
use constant CRC_LO => [
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5,
    0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B,
    0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE,
    0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6,
    0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
    0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8,
    0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
    0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21,
    0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A,
    0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
    0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7,
    0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51,
    0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
    0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D,
    0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
    0x41, 0x81, 0x80, 0x40
];

sub modbus_crc16 {
	my ($msg) = @_;

	my $crcl  = 0xFF;
	my $crch  = 0xFF;
	my @bytes = split(//, $msg);
	for (@bytes) {
		my $data = unpack('C*', $_);
		my $crcIdx = $crcl ^ $data;
		$crcl = $crch ^ &CRC_HI->[$crcIdx];
		$crch = &CRC_LO->[$crcIdx];
	}
 
	# Pack the trailer - CRC has low byte first
	my $trlr = pack('CC', $crcl, $crch);

	return $trlr;
}

our $protocol;
while(<DATA>) {
	chomp;
	next if m{^\s*$}; # skip empty lines
	my @a = split(/\s*\t\s*/, $_,7);
	my $id = hex( $a[0] );
	my $pack_fmt = $a[2]; $pack_fmt =~ s/\s+.*$//;
	$protocol->{$id} = {
		description => $a[1],
		pack_fmt => $pack_fmt,
		r_w => $a[3],
		range => $a[4],
		default => $a[5],
		remark => $a[6],
		line => [ @a ],
	};
}

sub read_parameter_frame {
	my $params = join('', @_);

	my $frame
		= "\x5A" # header
		. "\x0B" # protocol version
		. "\x03" # function code
		. pack("v", length($params)) # data length
	;

	$frame .= $params;

	return $frame . modbus_crc16($frame);
}

1;

# 6. Protocol Format 
# Valid data ID and parameter range supported by the product 
# DATA ID	ID Description	Data Type( Data Length） 	R/W 	Range 	Default 	Remark 

__DATA__
0x00	Seq #				l DWord(4)	R	/		0	The platform can carry the ID when the platform downstream reads and sets the device parameters, and the device returns the same data. Please refer to the example for use.Each seq # refer to one command and its response. 
0x01	PN				l DWord(4) 	R 	/		 / 	/ 
0x02 	Model 				C Byte(1) 	R	32 		32	Inner number:32 
0x03 	X axis angle 			f Float(4) 	R	 ‐90°~90° 	/ 	X axis angle 
0x04	Y axis angle 			f Float(4) 	R	‐90°~90° 	/ 	Y axis angle 
0x09	X axis relative angle		f Float(4)	R 	‐90°~90° 	0 	Return the X angle value according to the set relative zero 
0x0A	Y axis relative angle		f Float(4)	R 	‐90°~90° 	0	Return the Y angle value according to the set relative zero 
0x0C	Sensor temperature		s Word(2) 	R 	‐32768~32767 	/ 	signed,sensor temperature =Data/100, unit ℃ 
0x0D	Power source voltage 		S Word(2) 	R 	0~65535 	/ 	Voltage= Data/100, unit V 
0x0E	Heartbeat interval 		l DWord(4) 	R/W	60~2160000	86400 	ZCT330E Interval at which the device periodically uploads data to the server 
0x0F	Failure interval 		l DWord(4) 	R/W	60~2160000	3600 	ZCT330E Interval at which the device failure is retransmitted
0x11	Arming/disarming 		C Byte(1) 	R/W	0~255 		1 	0 means disarming, non‐zero means arming 
0x12 	Alarm delay time 		C Byte(1) 	R/W	3~255 		20	ZCT330M The unit is 0.1 second, which means that the product responds to the alarm only after the alarm has exceeded the alarm angle for a certain period of time. (mot used by ZCT330E, only ZCT330M) 

0x13	Restore factory setting		C Byte(1) 	R/W	0~255 		0 	0: Do nothing Non‐zero: Restore the non‐network‐related parameters of the sensor. 
0x14 	Server IP&port 			CCCCs 4*Byte(1)+Word(2)	R/W 		/ 	CTIOT:117.60.157.137,5683 MQTT:0.0.0.0,0 	The server address should be IP, not the domain name; using the domain name may cause the connection server to be unstable and cause data loss. After the function is set, content of 0x34 will be cleared, if 0x14 and 0x34 is set at the same time, priority is given to 0x14 (passive upload)
0x17 	Signal strength 		C Byte(1) 	R 	10~34 		/ 	A larger value indicates a stronger signal 
0x18	Sensor operating mode 		C Byte(1) 	R/W	0 		0 	0: absolute angle, 1: relative angle, 2: vibration, ZCT330Mx sensor can only work in absolute measurement mode, ZCT330Ex supports all modes
0x19	Alarm axis 			C Byte(1) 	R 	0~3 		/ 	0: no alarm; 1: X‐axis alarm 2: Y axis alarm; 3: X/Y axis alarm at the same time 
0x1A 	SIM card ID 			Q QWord(8) 	R 	0~18446744073709551615 	/ 	Take the first 19 digits, the last digit is discarded 
0x1B	Alarm angle			f Float(4)	R/W	0.06-30.00	5.0	ZCT330E relative mode, passive upload
0x1C	Alarm trigger time		l Dword(4)	R/W	2-20		2	ZCT330E relative mode, passive upload
0x1D	Static angle			f Float(4)	R/W	0.06-30.00	2.5	ZCT330E relative mode, passive upload
0x1E	Static trigger time		l Dword(4)	R/W	2-20		2	ZCT330E relative mode, passive upload
0x1F	Acquisition interval		l Dword(4)	R/W	1-2160000	60	ZCT330E absolute mode, passive upload
0x20	Failure retransmission		l Dword(4)	R/W	20-2160000	3600	ZCT330E absolute mode, passive upload
0x21	Heartbeat interval 		l DWord(4) 	R/W	60~131071	86400 	Interval at which the device periodically uploads data to the server 
0x22 	IMEI number of the device 	Q QWord(8)	 R	0~18446744073709551615	/ 	Refers to the IMEI of the NB‐IOT network module in the product. 
0x23	Backup server IP&port 		CCCCs 4*Byte(1)+Word(2)	R/W 	/ 	CTIOT:117.60.157.137,5683 MQTT:0.0.0.0,0 	/ if set, clears 0x3b, if 0x23 and 0x3b are set at same time, 0x23 has priority
0x24 	Backup server enable 		C Byte(1) 	R/W	0~255 	0 	0 means off, non‐zero means on 
0x33	DNS IP address 			CCCC 4*Byte(1) 	R/W 	/ 	208.67.222.222 	/ 
0x34	Domain name and port 		a* 64*Byte(1) 	R/W 	/ 	mqtt.zc‐sensor.com,1883	Supports CTIOT and MQTT protocols. Priority IP in the case of IP (ID number 0x14); domain name and port should be separated with comma, length <=64, cleared by setting 0x14
0x35	MQTT-ClientID 			a* 32*Byte(1) 	R/W	 / 	IMEI number of the device 	Length <=32, subject to MQTT related specifications 
0x36 	MQTT-Username 			a* 32*Byte(1) 	R/W 	/ 	empty 		Length <=32, subject to MQTT related specifications 

0x37	MQTT-Password 			a* 32*Byte(1) 	R/W 	/ 	empty 		Length <=32, subject to MQTT related specifications 
0x38	MQTT-published topic name	a* 128*Byte(1) 	R/W 	/ 	Inclinometer/ZCT330Mx_SWP_N_YK/IMEI/up 	Length <=128, subject to MQTT related specifications 
0x39	MQTT-subscribed topic name 	a* 128*Byte(1) 	R/W 	/ 	Inclinometer/ZCT330Mx_SWP_N_YK/IMEI/down 	Length <=128, subject to MQTT related specifications 
0x3A 	Set relative zero command 	C Byte(1) 	R/W	0~255 	0 		0: absolute angle mode 1: Set the current position to zero, relative angle mode (0x09, 0x0A content will be set to the current angle), Other values are invalid. 
0x3B	Backup server domain name and port 	a* 64*Byte(1) 	R/W 	/ 	mqtt.zc‐sensor.com,1883 	Supports CTIOT and MQTT protocols. In the case of backup IP, the IP is preferentially backed up; the domain name and port are distinguished by commas, and the length is <=64. , will be cleared if 0x23 is set
0x3D	Protocol type 			C Byte(1) 	R/W	0~1 	CTIOT 	0:CTIOT 1:MQTT Other values are invalid. 
0x3E	Alarm angle			f Float(4)	R/W	0.06-30.00	2.0	ZCT330E vibration mode, passive upload
0x3F	Alarm trigger time		l Dword(4)	R/W	80-20000	240	ZCT330E vibration mode, passive upload (trigger time in ms)
0x40	Static angle			f Float(4)	R/W	0.06-30.00	1.0	ZCT330E vibration mode, passive upload
0x41	Static trigger time		l Dword(4)	R/W	80-20000	240	ZCT330E vibration mode, passive upload (trigger time in ms)
0x42	Heartbeat interval 		l DWord(4) 	R/W	60~2160000	86400 	ZCT330E (unit: s) vibration mode, Interval at which the device periodically uploads data to the server 
0x43	Failure retransmission		l Dword(4)	R/W	20-2160000	3600	ZCT330E (unit: s) vibration mode, passive upload
0x44	Alarm angle 			f Float(4) 	R/W	ZCT330M:‐90°~90°,ZCT330E:0.06-30.00 	ZCT330M:3°,ZCT330E:5.0 	X/Y axis alarm angle is consistent, ZCT330E in absolue mode, passsive upload 
0x45	Alarm trigger time		l Dword(4)	R/W	0-3600		0	ZCT330E Sensor waiting time for the server to issue a command (s), passive upload

