JavaLikeCalc.JavaScript
function CRC( inSeq ) { return Special.FLibSYS.CRC(inSeq, 16, 0x1021, -1, true, true, 0xFFFF); }

function ASN1_Data( inSeq, offIn ) {
	val = EVAL;
	offIn += 1;
	if((vTp=inSeq.charCodeAt(offIn-1)) == 0)	//NULL
		val = 0;
	else if(vTp == 1 || vTp == 2) {		//Array, Structure
		itSz = inSeq.charCodeAt(offIn);
		val = new Array(itSz);
		offIn += 1;
		for(itI = 0; itI < itSz; itI++)
			val[itI] = ASN1_Data(inSeq, offIn);
	}
	else if(vTp == 4 || vTp == 18) {		//BIT STRING, Unsigned16
		val = inSeq.charCodeAt(offIn, "UTF-16BE");
		offIn += 2;
	}
	else if(vTp == 5) {							//Integer32
		val = inSeq.charCodeAt(offIn, "UTF-32BE");
		if(val > 2147483647) val -= 4294967296;
		offIn += 4;
	}
	else if(vTp == 6) {							//Unsigned32
		val = inSeq.charCodeAt(offIn, "UTF-32BE");
		offIn += 4;
	}
	else if(vTp == 9 || vTp == 10) {		//OCTET STRING, VisibleString
		itSz = inSeq.charCodeAt(offIn);
		val = inSeq.slice(offIn+1, offIn+1+itSz);
		offIn += 1+itSz;
	}
	else if(vTp == 13 || vTp == 15) {		//Integer8
		val = inSeq.charCodeAt(offIn);
		if(val > 127) val -= 256;
		offIn += 1;
	}
	else if(vTp == 16) {						//Integer16
		val = inSeq.charCodeAt(offIn, "UTF-16BE");
		if(val > 32767) val -= 65536;
		offIn += 2;
	}
	else if(vTp == 17 || vTp == 22) {		//Unsigned8, ENUMERATED
		val = inSeq.charCodeAt(offIn);
		offIn += 1;
	}
	else if(vTp == 18) {						//Unsigned16
		val = inSeq.charCodeAt(offIn, "UTF-16BE");
		offIn += 2;
	}
	else {
		SYS.messDebug("/IEC62056", tr("Unknown data type %1, termination").replace("%1",vTp.toString()));
		offIn = inSeq.length;
	}

	return val;
}

function inVal( aId, aVal ) {
	aId_ = aId.replace(".", "_"); 
	if(items[aId_].isEVal()) {
		items[aId_] = itW = new Object();
		itW.descr = aId;
		itW.wr = false; itW.alarm = 0;
	} else itW = items[aId_];
	itW.val = aVal;
	if((aO=this[aId_]).isEVal()) {
		if((aTp=typeof(aVal)) == "array")	aTp = "object";
		else if(aTp == "real") ;
		else if(aTp == "int")	aTp = "integer";
		else aTp = "string";
		this.attrAdd(aId_, aId, aTp+",ro");
		aO = this[aId_];
	}
	aO.set(aVal, 0, 0, true);
}

function processIn( isSync ) {
	t_err = "";
	//Parse for packages in general - the transport layer
	for(waitSz = 0; ctx.in.length; ctx.in = ctx.in.slice(waitSz)) {
		// Checking for very short
		if(ctx.in.length < 9)	{ if(isSync == true) return "10:"+tr("Too short"); break; }
		// Checking the header entirety
		if(ctx.in[0] != "~" || ((form=ctx.in.charCodeAt(1))&0xF0) != 0xA0 || ((dataSz=ctx.in.charCodeAt(1,"UTF-16BE")&0x07FF) < 7) ||
				//  Checking the source address
				!((src_=ctx.in.charCodeAt(3))&1) || (src_>>1) != src) {
			SYS.messDebug("/IEC62056",tr("Wrong header or source address, rejected")+": "+SYS.strDecode(ctx.in,"Bin"," "));
			if(isSync == true) return "11:"+tr("Wrong header or source address, rejected");
			ctx.in = "";
			break;
		}
		//  Checking the destination address
		if(((dst_=ctx.in.charCodeAt(4))&1) && (dst_>>1) == ctx.destUp && ctx.destLow == 0)
			dst_sz = 1;
		else if(!((dst_=ctx.in.charCodeAt(4))&1) && ((dst_2=ctx.in.charCodeAt(5))&1) && (dst_>>1) == ctx.destUp && (dst_2>>1) == ctx.destLow)
			dst_sz = 2;
		else if(!((dst_=ctx.in.charCodeAt(4))&1) && !(ctx.in.charCodeAt(5)&1) && !((dst_2=ctx.in.charCodeAt(6))&1) && (ctx.in.charCodeAt(7)&1) &&
				(((dst_>>1)<<7)|(ctx.in.charCodeAt(5)>>1)) == ctx.destUp && (((dst_2>>1)<<7)|(ctx.in.charCodeAt(7)>>1)) == ctx.destLow)
			dst_sz = 4;
		else {
			SYS.messDebug("/IEC62056",tr("Wrong destination address, rejected")+": "+SYS.strDecode(ctx.in,"Bin"," "));
			if(isSync == true) return "11:"+tr("Wrong destination address, rejected");
			ctx.in = "";
			break;
		}
		// Short yet, waiting
		if(ctx.in.length < (7+dst_sz))	{ if(isSync == true) return "10:"+tr("Too short"); break; }
		//  Checking the header CRC
		if(CRC(ctx.in.slice(1,5+dst_sz)) != ctx.in.charCodeAt(5+dst_sz,"UTF-16LE")) {
			SYS.messDebug("/IEC62056",tr("CRC-error, rejected")+": "+SYS.strDecode(ctx.in,"Bin"," "));
			if(isSync == true) return "11:"+tr("CRC-error, rejected");
			ctx.in = "";
			break;
		}
		// Checking the frame entirety
		//  Not full yet, waiting
		if(ctx.in.length < (waitSz=2+dataSz)) { if(isSync == true) return "10:"+tr("Too short"); break; }
		if(ctx.in[waitSz-1] != "~") {
			SYS.messDebug("/IEC62056",tr("The end symbol is wrong")+": "+SYS.strDecode(ctx.in,"Bin"," "));
			if(isSync == true) return "11:"+tr("The end symbol is wrong");
			ctx.in = "";
			break;
		}
		//  Checking the frame CRC with the data
		if(dataSz > (6+dst_sz) && CRC(ctx.in.slice(1,1+dataSz-2)) != ctx.in.charCodeAt(1+dataSz-2,"UTF-16LE")) {
			SYS.messDebug("/IEC62056",tr("CRC-error, rejected")+": "+SYS.strDecode(ctx.in,"Bin"," "));
			if(isSync == true) return "11:"+tr("CRC-error, rejected");
			ctx.in = "";
			break;
		}

		SYS.messDebug("/IEC62056",tr("Good input sequence")+": "+SYS.strDecode(ctx.in.slice(0,waitSz),"Bin"," "));

		cntr = ctx.in.charCodeAt(4+dst_sz);
		// Checking the control byte, sequinces and the frame segments
		if((cntr&0x7) == 1) {	//RR
			SYS.messDebug("/IEC62056", "RR"+(cntr>>5)+((cntr&0x10)?"F":""));
			if(ctx.reqToResp == "RR")	ctx.reqToResp = "";
			continue;
		}
		else if(!(cntr&1)) {	//I
			SYS.messDebug("/IEC62056", "I"+((cntr>>1)&0x7)+","+(cntr>>5)+((cntr&0x10)?"F":""));
			if(ctx.sendSeq != (cntr>>5)) {
				SYS.messDebug("/IEC62056", tr("Broken sequence %1, rejected").replace("%1",ctx.sendSeq.toString()));
				continue;
			}
			if(ctx.readSeq >= 7) ctx.readSeq = 0; else ctx.readSeq++;
			if(cntr&0x10)	ctx.sendRR = true; //mess("RR");		//F - final
		}

		ctx.inAMess += ctx.in.slice(7+dst_sz, 1+dataSz-2);
		if(form&0x8)	continue;	//Waiting the next segment

		// Getting the data
		if(cntr == 0x73)	{	//UA
			SYS.messDebug("/IEC62056", "UA");
			if(ctx.toInit == 1 && ctx.reqToResp == "SNRM")	{
				ctx.toInit++;
				//!!!! Parsing the SNRM result
			}
			ctx.reqToResp = "";
		}
		else if((cntr&0x11) == 0x10) {	//I,F
			if(ctx.inAMess.slice(0,4) == SYS.strFromCharCode(0xE6,0xE7,0x00,0x61)) {	//LPDU, APPLICATION 1, Initial parameter acquiring
				if(ctx.inAMess.indexOf(SYS.strFromCharCode(0xA2,0x03,0x02,0x01,0x00)) >= 0) ctx.toInit++;
				else { t_err += tr("Error the authentication")+"; "; tr.start(false); }
				//!!!! Deep parsing the initial parameter acquiring
			}
			else if(ctx.inAMess.slice(0,5) == SYS.strFromCharCode(0xE6,0xE7,0x00,0xC4,0x01)) {	//LPDU, GET-Response-Normal
				tVl = ctx.inAMess.charCodeAt(5);		//Invoke-Id-And-Priority
				// Get-Data-Result
				if((tVl=ctx.inAMess.charCodeAt(6)) == 0) {	// Data
					val = ASN1_Data(ctx.inAMess, 7);
					//Processing the result
					// Array of the variables with OBIS and values
					if(typeof(val) == "array" && val.length == 1 && typeof(val[0]) == "array" && val[0].length && typeof(val[0][0]) == "array" && val[0][0].length == 3)
						for(iIt = 0; iIt < val[0].length; iIt++) {
							wIt = val[0][iIt];
							tVl = val[0][iIt][0];
							for(iIt_OBIS = "", iN = 0; iN < tVl.length; iN++)
								iIt_OBIS += (iIt_OBIS.length?".":"") + tVl.charCodeAt(iN).toString();
							inVal(iIt_OBIS, val[0][iIt][1]*pow(10,val[0][iIt][2][0]));
						}
					// Single variable
					else inVal(ctx.reqOBIS, val);
					SYS.messDebug("/IEC62056", "Data value: "+typeof(val));
				}
				else if(tVl == 1)										// Error
					t_err += tr("Error the data %1").replace("%1",ctx.inAMess.charCodeAt(7).toString())+"; ";
			}
		}
		ctx.inAMess = "";
	}
	return "0"+(t_err.length?":"+t_err:"");
}

//Forming of the message and placing it to the output buffer
//  Commands: "SNRM", "RR", "I", "DISC"
function mess( com, data ) {
	if(data.isEVal())	data = "";
	mLen = 10 + (data.length ? data.length+2 : 0);
	 //SYS.strFromCharCode(ac, func) + objs;
	aMess = SYS.strFromCharCode(0xA0|(mLen>>8), mLen&0xFF);
	if(!ctx.destLow && ctx.destUp < 128)					aMess += SYS.strFromCharCode(((ctx.destUp&0x7F)<<1)|1);
	else if(ctx.destUp < 128 && ctx.destLow < 128)	aMess += SYS.strFromCharCode((ctx.destUp&0x7F)<<1, ((ctx.destLow&0x7F)<<1)|1);
	else aMess += SYS.strFromCharCode((ctx.destUp>>7)<<1, (ctx.destUp&0x7F)<<1, (ctx.destLow>>7)<<1, ((ctx.destLow&0x7F)<<1)|1);
	aMess += SYS.strFromCharCode((src<<1)|1);
	ctx.reqToResp = com;
	if(com == "SNRM")		aMess += SYS.strFromCharCode(0x93);
	else if(com == "RR")	aMess += SYS.strFromCharCode((ctx.readSeq<<5)|0x11);
	else if(com == "DISC")	aMess += SYS.strFromCharCode(0x53);
	else if(com == "I") {
		aMess += SYS.strFromCharCode(0x10|(ctx.readSeq<<5)|(ctx.sendSeq<<1));
		if(ctx.sendSeq >= 7) ctx.sendSeq = 0; else ctx.sendSeq++;
	} else return;	
	aMess += SYS.strFromCharUTF("UTF-16LE",CRC(aMess)) + data;
	if(data.length)	aMess += SYS.strFromCharUTF("UTF-16LE",CRC(aMess));
	aMess = SYS.strFromCharCode(0x7E) + aMess + SYS.strFromCharCode(0x7E);
	ctx.out += aMess;
	//Just direct writing per one message
	//SYS.messDebug("/IEC62056",tr("Good output sequence")+": "+SYS.strDecode(aMess,"Bin"," "));
	//tr.messIO(aMess, 0, 0);

	//Synchronous request-response
	SYS.messDebug("/IEC62056",tr("Good output sequence")+": "+SYS.strDecode(ctx.out,"Bin"," "));
	t_err = "0";
	ctx.in = tr.messIO(ctx.out); ctx.out = "";
	if(!ctx.in.length)	{ t_err = "10:"+tr("No response"); tr.start(false); }
	else while((t_err=processIn(true)).toInt() == 10 && (respTail=tr.messIO("")).length) ctx.in += respTail;

	return t_err;
}

if(f_start) {
	itemsSet_ = "";
	items = new Object();
	//oAVals = new Object();
	transport_ = "", tr = EVAL;
	tmRetr_ = tmResp_ = tmRetr;
	tmPollAll_ = tmPollAll; offPoll = 0;
	ctx = new Object();

	return;
}

t_err = "0";

if(f_stop)	t_err = mess("DISC");

//Items set changing process
if(itemsSet != itemsSet_) {
	itemsSet_ = itemsSet;
	tmPollAll_ = tmPollAll; offPoll = 0;		//and reread
}

//Check for the transport change and connect
destUp = dest.parse(0,":").toInt(); destLow = dest.parse(1,":").toInt();
if(tr.isEVal() || transport != transport_)	{
	itemsSet_ = "";
	items = new Object();
	//oAVals = new Object();
	tr = SYS.Transport.nodeAt(transport, ".");
	transport_ = transport;
	if(!tr.isEVal()) tr.start(false);
	tmRetr_ = tmResp_ = tmRetr;
	tmPollAll_ = tmPollAll; offPoll = 0;
}
if(tr.isEVal())	t_err = "1:"+tr("Output transport '%1' error.").replace("%1",transport);
else if(destUp < 0 || destUp > 16383)
	t_err = "2:"+tr("Upper HDLC destination address '%1' out of range [0...16383].").replace("%1",destUp.toString());
else if(destLow < 0 || destLow > 16383)
	t_err = "2:"+tr("Lower HDLC destination address '%1' out of range [0...16383].").replace("%1",destUp.toString());
else if(src < 0 || src > 127)
	t_err = "2:"+tr("Source address '%1' out of range [0...127].").replace("%1",src.toString());
else if(!tr.start()) {
	if(tmRetr_ >= tmRetr) {
		tr.start(true);
		itemsSet_ = "";
		tmRetr_ = tmResp_ = 0;
		tmPollAll_ = tmPollAll; offPoll = 0;
		ctx = new Object();
		ctx.destUp = destUp; ctx.destLow = destLow;
		pass_ = pass;
	} else tmRetr_ += 1/f_frq;
	if(!tr.start())	t_err = "1:"+tr("Output transport '%1' stoped.").replace("%1",transport);
}
else if(ctx.destUp != destUp || ctx.destLow != destLow || pass != pass_)	tr.start(false);
else {
	//Init the context
	if(ctx.in.isEVal()) {
		ctx.in = ctx.out = ctx.inAMess = "";
		ctx.sendSeq = ctx.readSeq = 0;
		ctx.toInit = 1;
		ctx.firSeq = -1;
		ctx.sendRR = false;
		ctx.reqToResp = "";
	}

	//Send output buffer and read the transport for an input data
	// Asynchronous mode
	//if(ctx.out.length) SYS.messDebug("/IEC62056",tr("Good output sequence")+": "+SYS.strDecode(ctx.out,"Bin"," "));
	//ctx.in += tr.messIO(ctx.out, -0.001);
	//ctx.out = "";

	//Input processing
	//t_err = processIn();

	//Response timeout
	/*if(ctx.reqToResp.length) {
		if(tmResp_ > tmRetr)	tr.start(false);	//No response long time
		tmResp_ += 1/f_frq;
	}
	// Initial parameters setting
	else*/
	if(!t_err.toInt() && ctx.toInit == 1)
		t_err = mess("SNRM", SYS.strFromCharCode(
			0x81,0x80,0x12,		//format and group identifier, length 18 bytes
				0x05,0x01,0x80,	//the parameter "Maximum information field length - transmit" = 128
				0x06,0x01,0x80,	//the parameter "Maximum information field length - receive" = 128
				0x07,0x04,0x00,0x00,0x00,0x01,	//the parameter "Window size - transmit" = 1
				0x08,0x04,0x00,0x00,0x00,0x07	//the parameter "Window size - receive" = 7
			));
	// Initial parameter acquiring
	if(!t_err.toInt() && ctx.toInit == 2)
		t_err = mess("I", SYS.strFromCharCode(
			0xE6,								//Destination (remote) LSAP, fixed in COSEM
			0xE6,								//Source (local) LSAP, fixed in COSEM E6 for command E7 for response
			0x00,								//Quality, Control byte, Reserved and 00 always
			0x60, 46+pass.length,		//APPLICATION 0, Length 54
			0xA1, 0x09, 0x06, 0x07,	//Application-context-name [1] (9 byte), OBJECT IDENTIFIER (7 byte)
				0x60, 0x85, 0x74, 0x05, 0x08, 0x01, 0x01,
			0x8A, 0x02, 0x07, 0x80,	//ACSE-requirements [10], BIT STRING (7) = 0x80
			0x8B, 0x07,						//Security Mechanism-name [11]
				0x60, 0x85, 0x74, 0x05, 0x08, 0x02, 0x01,
			0xAC, pass.length+2, 0x80, pass.length) + //Authentication-value [12], GraphicString (0x80), Key value
				pass + SYS.strFromCharCode(
			0xBE, 0x10, 0x04, 0x0E,	//Association-information [30], Client information, OCTET STRING (4)
				0x01,							// Data tag for the request
				0x00, 0x00, 0x00,		// Flags field
				0x06, 0x5F, 0x1F,		// Application tag, 31
				0x04, 0x00, 0x00, 0x18, 0x1D,	// Client side services
				0x00, 0x00					// PDU maximum size
			));
	if(!t_err.toInt() && ctx.toInit > 2) {
		if(ctx.sendRR)	{ t_err = mess("RR"); ctx.sendRR = false; }
		//Polling
		// Read all items
		if(!t_err.toInt() && tmPollAll_ > tmPollAll) {
			if((iIt=itemsSet.parse(0,"\n",offPoll)).length) {
				iIt_ClassId = iIt.parse(0, "-");
				iIt_OBIS = iIt.parse(1, "-");
				iIt_a = iIt.parse(2, "-");
				ctx.reqOBIS = iIt_OBIS;
				t_err = mess("I", SYS.strFromCharCode(0xE6, 0xE6, 0x00,
					0xC0, 0x01, 0x81,	//Get-Request-Normal, Invoke-Id-And-Priority
					0x00, iIt_ClassId.toInt(),	//Cosem-Class-Id, ?
					iIt_OBIS.parse(0,".").toInt(), iIt_OBIS.parse(1,".").toInt(), iIt_OBIS.parse(2,".").toInt(), iIt_OBIS.parse(3,".").toInt(), iIt_OBIS.parse(4,".").toInt(), iIt_OBIS.parse(5,".").toInt(),
					iIt_a.toInt(),		//Cosem-Object-Attribute-Id
					0x00						//Selective-Access-Descriptor, 0
				));
			}
			if(offPoll >= itemsSet.length) { tmPollAll_ = 0; offPoll = 0; }
		}

		tmPollAll_ += 1/f_frq;
		tmResp_ = 0;
	}
}

//Error set
if(t_err.toInt()) {
	if(!tr.isEVal() && tr.start()) tr.start(false);
	if(f_err != t_err) {
		for(var iIt in items)
			this[iIt].set(EVAL_REAL, 0, 0, true);
		items = new Object();
		SYS.messDebug("/IEC62056", tr("Error")+": "+t_err);
	}
}
f_err = t_err;

if(f_stop && !tr.isEVal()) tr.start(false);