// 2600 Design Assistant, by Jim Lamberson
// Copyright (C) 2007 Sensoray

// Form fields.
var Fields = new Array( 'ains', 'aouts', 'dins', 'douts', 'encs', 'cntrs', 'fcntrs', 'gages','timers',
	'rly240', 'rly120', 'ssr240o', 'ssr240i', 'ssr120o', 'ssr120i', 'ssr60o', 'ssr32i', 'sios' );

// Global storage.
for ( i = 0; i < Fields.length; i++ ) eval( "var " + Fields[i] );						        // app i/o requirements
var nb2601,nb2608_0,nb2608_4,nb2608_8,nb2610,nb2612,nb2620,nb2650,nb2652,nb2653,nbpwr,nb2600c1,ncat5;	// bom items
var nb2608,nbssr,mA;																	        // other resources

// UTILITIES //////////////////////////////////////////////////

function isDigit( c )
{
	return ( ( c >= "0" ) && ( c <= "9" ) )
}

function isInteger( s )
{
	var i;
	
	for ( i = 0; i < s.length; i++ )
	{
		if ( !isDigit( s.charAt( i ) ) )
			return false;
	}
	
	return true;
}

// Return input value, or -1 if not valid.
function ValueOf( FieldName )
{
	var field = document.getElementById( FieldName )
	
	// If html field doesn't exist ..
	if ( field == null )
		return 0;
	
	var s = field.value;

	// Strip leading/trailing spaces.
	if ( ( s = (s.replace(/^\W+/,'')).replace(/\W+$/,'') ) == '' )
		return 0;
	
	if ( !isInteger( s ) )
	{
		alert( "ERROR: Value must be a whole number in the range 0 to 999999." );
		field.focus();
		return -1;
	}
		
	var num = parseInt( s, 10 );

	return ( (( num >= 0 ) && ( num <= 999999 )) ? num : -1 );
}

function CurrencyFormatted(amount)
{
	var i=parseFloat(amount);
	if(isNaN(i)) i=0.00;
	s=new String(parseInt((i + .005) * 100) / 100);
	if(s.indexOf('.') < 0) s += '.00';
	if(s.indexOf('.') == (s.length - 2)) s += '0';
	return s;
}

function CommaFormatted(amount)
{
	var a=String(amount).split('.',2)
	var d=a[1];
	var i=parseInt(a[0]);
	if(isNaN(i)) return '';
	if(isNaN(parseInt(d))) d='';
	var n=new String(i);
	a = [];
	while(n.length > 3)
	{
		var nn = n.substr(n.length-3);
		a.unshift(nn);
		n = n.substr(0,n.length-3);
	}
	if(n.length > 0) a.unshift(n);
	n = a.join(',');
	return (d.length < 1)?n:n+'.'+d;
}

function Formatted(amount)
{
	return CommaFormatted( CurrencyFormatted( amount ) );
}

// Blank all form fields.
function ResetSystem()
{
	for ( i = 0; i < Fields.length; i++ )
		document.getElementById( Fields[i] ).value = '';
	
	BuildSystem();
}

// Compute app solution and display it.
function BuildSystem()
{
	// Fetch and validate the i/o quantity fields.
	for ( i = 0; i < Fields.length; i++ )
	{
		if ( eval( Fields[i] + '= ValueOf( "' + Fields[i] + '" )' ) == -1 )
		{
			for ( i = 0; i < Fields.length; i++ ) eval( Fields[i] + '=0' );
			break;
		}
	}

	// Compute unit quantities ----------------------------

	// 2608s.
	var nbaout = parseInt( ( 7 + aouts ) /  8 );
	var nbain =  parseInt( ( 15 + ains ) / 16 );
	nb2608 = ( ( nbaout > nbain ) ? nbaout : nbain );
	nb2608_8 = ( aouts + 3 ) >> 3;
	nb2608_4 = ( ( aouts + 3 ) >> 2 ) & 1;
	nb2608_0 = nb2608 - nb2608_8 - nb2608_4;

    // Relays racks.
	nbssr = ssr240o + ssr240i + ssr120o + ssr120i + ssr60o + ssr32i;
	nb2652 = parseInt( ( 7 + rly120 + nbssr ) / 8 );
	nb2653 = parseInt( nb2652 / 2 );
    nb2652 -= ( 2 * nb2653 );

	// Other iom types.
	nb2610 = parseInt( ( 47 + dins + douts ) / 48 );
	nb2612 = parseInt( ( 3 + gages ) / 4 );
	nb2620 = parseInt( ( 3 + encs + cntrs + fcntrs + timers ) / 4 );
	nb2650 = parseInt( ( 7 + rly240 ) / 8 );
   
	// Main modules.
	var niom = ( nb2608 + nb2610 + nb2612 + nb2620 + nb2650 + nb2652 + nb2653 );
	var n2601iom = ( niom + 15 ) >> 4;
	var n2601ser = ( sios + 3 ) >> 2;
	nb2601 = ( ( n2601iom > n2601ser ) ? n2601iom : n2601ser );

	// Power supplies.
	var PWR2601 = 130;		// Current requirements (mA).
	var PWR2608 = 80;
	var PWR2610 = 120;
	var PWR2612 = 80;		// including 5V power supplied to four 350 ohm gauges
	var PWR2620 = 30;
	var PWR2650 = 310;
	var PWR2650 = 310;
	var PWR2652 = 30;
	var PWR2653 = 35;   // ### PRELIMINARY
    var PWRssr = 10;
    var PWRrly = 16;
    
	mA = nb2601*PWR2601 + nb2608*PWR2608 + nb2610*PWR2610 + nb2612*PWR2612 + nb2620*PWR2620 + nb2650*PWR2650 + nb2652*PWR2652 + nb2653*PWR2653 + nbssr*PWRssr + rly120*PWRrly;
	nbpwr = Math.floor( ( mA / 1000 + 4.999999 ) / 5 );

	// 2600C1 power daisy-chain cables.
	nb2600c1 = nb2610 + nb2650 + nb2652 + nb2653;

	// Cat-5 cables.
	ncat5 = niom + nb2601;
	
	// Render everything.
	RenderBom();			// bom
	RenderSurplus();		// surplus resources
	RenderWiringDiagram();	// wiring diagram
}

// Display surplus resources.
function RenderSurplus()
{
	// Generate description of surplus resource.
	function surplus( n, descrA, descrB )
	{
		return ( n == 0 ) ? '' : ( '<li class="bodytext">' + n + ' ' + descrA + ( ( n > 1 ) ? 's ' : ' ' ) + descrB + '</li>' );
	}

	// Compute total resources.
	var taouts = 8 * nb2608_8 + 4 * nb2608_4;
	var tains = 16 * nb2608;
	var tgages = 4 * nb2612;
	var tsios = 4 * nb2601;
	var tdios = 48 * nb2610;
	var tgages = 4 * nb2612;
	var trlys = 8 * nb2650;
	var tssrs = 8 * nb2652 + 16 * nb2653;
	var tcntrs = 4 * nb2620;
	var tmA = nbpwr * 5000;
	
	// Display the surplus resources.
	document.getElementById( 'surplus' ).innerHTML = ( nb2601 == 0 ) ? '&nbsp;'
		: '<ul style="margin:5px auto">'
		+ surplus( taouts - aouts, 'analog output', '' )
		+ surplus( tains - ains, 'analog input', '' )
		+ surplus( tgages - gages, 'strain gage input', '' )
		+ surplus( tdios - dins - douts, 'digital I/O', ', independently configurable as input or output' )
		+ surplus( trlys - rly240, '240V relay', '' )
		+ surplus( tssrs - nbssr - rly120, 'SSR socket', '' )
		+ surplus( tcntrs - encs - cntrs - fcntrs - timers, 'counter/timer/encoder channel', '' )
		+ surplus( tsios - sios, 'serial communication port', ' (RS-232/RS-422/RS-485, software selectable)' )
		+ "<li class='bodytext'>Total 24VDC current is " + CommaFormatted( mA ) + " mA, leaving " + CommaFormatted( tmA - mA ) + " mA available for other use</li>"
		+ '</ul>';
}

function RenderWiringDiagram()
{
	var BLACK = "#000000";
	var BOX_COLOR = "#EFF0F1";	// Lt.Grey Blue

	var TOP_MARGIN = 30;
	
	var TXT_FIELDHORZGAP = 10;
	
	var TXT_TITLEWIDTHLEFT = 50;	// Width of text boxes for field wiring label
	var TXT_TITLEWIDTHRIGHT = 150;
	
	var MM_WIDTH = 60;
	var ETH_CABLELEN = 60;
	var SIO_HEIGHT = 20;
	var MM_LEFTMARGIN = TXT_TITLEWIDTHLEFT + TXT_FIELDHORZGAP + ETH_CABLELEN;
	var MM_RIGHTMARGIN = MM_LEFTMARGIN+MM_WIDTH;
	
	var CBL_VOFFSET = 4;
	var IOM_WIDTH = 60;
	var IOM_HEIGHT = 40;
	var IOM_GAP = 10;
	var IOM_CABLELEN = 80;
	var IOM_DAISYLEN = 15;
	var IOM_LEFTMARGIN = MM_RIGHTMARGIN + IOM_CABLELEN;
	var IOM_RIGHTMARGIN = IOM_LEFTMARGIN + IOM_WIDTH;
	
	var BOX_SHADOW = 4;	// shadow offset
	var CBL_WIDTH = 2;
	
	var PS_GAP = 30;	// Gap between pwr supply & 2601
	
	var TXT_VGAP = 11;	// Space text above cable
	
	var LEN_FIELDWIRE = 30;

	// Top margin on diagram.
	var nIoms = nb2608_0 + nb2608_4 + nb2608_8 + nb2610 + nb2612 + nb2620 + nb2650 + nb2652 + nb2653;
	var DiagramHeight=nIoms*(IOM_HEIGHT+IOM_GAP)+(sios*SIO_HEIGHT)+PS_GAP+IOM_HEIGHT;
	var CanvasHeight=DiagramHeight+2*TOP_MARGIN;
	var TopMargin=(CanvasHeight-DiagramHeight)/2;
	
	var jg;

	var box = new Array();	// Box list;
	var cable = new Array();	// Cable list;
	var label = new Array();	// Field I/O label;
	
	// Box object --------------
	
	function objBox(txt,x,y,w,h)
	{
		this.txt=txt;
		this.x=x;
		this.y=y;
		this.w=w;
		this.h=h;
		
		label.push( new objLabel(txt, x, y+h/2, w, 12, "center", "center") );
	}
	
	objBox.prototype.drawBox = function ()
	{
		jg.setStroke(1);
		jg.setColor(BOX_COLOR);
		jg.fillRect(this.x,this.y,this.w,this.h);
		jg.setColor(BLACK);
		jg.drawRect(this.x,this.y,this.w,this.h);
	}
	
	objBox.prototype.drawShadow = function ()
	{
		jg.setStroke(1);
		jg.setColor("#CCCCCC");
		jg.fillRect(this.x+BOX_SHADOW,this.y+BOX_SHADOW,this.w,this.h);
		jg.drawRect(this.x+BOX_SHADOW,this.y+BOX_SHADOW,this.w,this.h);
	}
	
	// Cable object ----------------
	
	function objCable(txt,talign,x1,y1,x2,y2)
	{
		var fontsize=10;
		var xmargin=3;
		var txtwidth=100;
		
		this.txt=txt;
		this.x1=x1;
		this.y1=y1;
		this.x2=x2;
		this.y2=y2;
		
		if (this.txt!='')
		{
			switch(talign)	// top/bottom for horz cables, left/right for vert cables
			{
				case 'top':		label.push( new objLabel( txt, x1, y1-fontsize-CBL_WIDTH, x2-x1, fontsize, "center", "top" ) );	break;
				case 'left':	label.push( new objLabel( txt, x1-txtwidth-xmargin, (y1+y2)/2, txtwidth, fontsize, "right", "center" ) );	break;
				case 'right':	label.push( new objLabel( txt, x1+xmargin+CBL_WIDTH, (y1+y2)/2, txtwidth, fontsize, "left", "center" ) );	break;
			}
		}
	}
	
	objCable.prototype.draw = function ()
	{
		jg.setColor(BLACK);
		jg.setStroke(CBL_WIDTH);
		jg.drawLine(this.x1,this.y1,this.x2,this.y2);
	}
	
	// Label object ---------------------
	
	function objLabel(txt, x, y, w, fontsize, halign, valign)
	{
		this.txt=txt;
		this.x=x;
		this.y=y-6;	//???
		this.w=w;
		this.fontsize=fontsize;
		this.halign=halign;
		this.valign=valign;
	}
	
	objLabel.prototype.draw = function ()
	{
		var y;
		switch(this.valign){
			case "top":		y=this.y;break;
			case "center":	y=this.y-this.fontsize/2;break;
			case "bottom":	y=this.y-this.fontsize;
		}
		
		jg.setColor(BLACK);
		jg.setFont("arial,helvetica,sans-serif", this.fontsize, Font.BOLD);
		jg.drawStringRect(this.txt, this.x, y, this.w, this.halign);
	}

	// Object generator utilities -------------

	function CreateFieldIo(txt,cbllbl,x,y,cbllen,talign)	//(x,y) is cable end, align wrt cable end
	{
		cable.push( new objCable(cbllbl,'top',x,y,x+cbllen,y) );
		if (talign=="left")
			label.push( new objLabel(txt, x-TXT_TITLEWIDTHLEFT-TXT_FIELDHORZGAP, y, TXT_TITLEWIDTHLEFT, 10, "right", "center") );
		else
			label.push( new objLabel(txt, x+cbllen+TXT_FIELDHORZGAP+CBL_WIDTH, y, TXT_TITLEWIDTHRIGHT, 10, "left", "center") );
	}

	function CreateIom(iomtype,iolabel,x,y,needsPwrCbl,isFirstPwrCbl)
	{
		// Cat5 cable to 2601
		cy=y+IOM_HEIGHT/2;
		cable.push( new objCable('Cat5','top',x,cy,x+IOM_CABLELEN,cy) );
		// Daisy-chain iom power cable
		if(needsPwrCbl)
		{
			if (isFirstPwrCbl)
				cable.push( new objCable('2600C1','top',x,y+CBL_VOFFSET,x+IOM_CABLELEN,y+CBL_VOFFSET) );
			else
			{
				x2=x+IOM_CABLELEN;
				x1=x2-IOM_DAISYLEN;
				y2=y+CBL_VOFFSET;
				y1=y-IOM_HEIGHT-IOM_GAP+IOM_HEIGHT-CBL_VOFFSET;
				cable.push( new objCable('2600C1','left',x1,y1,x1,y2) );
				cable.push( new objCable('','',x1,y2,x2,y2) );
				cable.push( new objCable('','',x1,y1,x2,y1) );
			}
		}
		// Field wiring
		CreateFieldIo(iolabel,'',x+IOM_CABLELEN+IOM_WIDTH,y+IOM_HEIGHT/2,LEN_FIELDWIRE,"right");
		// Iom box
		box.push( new objBox(iomtype,x+IOM_CABLELEN,y,IOM_WIDTH,IOM_HEIGHT) );
		return IOM_HEIGHT+IOM_GAP;	// vertical spacing for next iom
	}

	function DrawDiagram()
	{
		if (nb2601!=1 || nbpwr>1)	//don't draw picture
			CanvasHeight=70;

		// Create canvas div and printable graphics context.
		document.getElementById("canvasCell").innerHTML = '<div id="canvas" style="position:relative;left:0px;top:0px;width:500px;height:' +  CanvasHeight  + 'px; z-index=2; "></div>';
		jg = new jsGraphics("canvas");
		jg.setPrintable(true);
		
		if (nb2601==0)
			return;
			
		if (nb2601>1 || nbpwr>1)	// TOO COMPLEX TO DRAW A PICTURE!
		{
			w=400;
			txt="Please contact a Sensoray Application Engineer";
			jg.setColor(BLACK);
			jg.setFont("arial,helvetica,sans-serif", "12px", Font.BOLD);
			jg.drawStringRect(txt,0,CanvasHeight/2-6,w,"center");
			jg.paint();
			return;
		}
		
		// Build list of graphics objects --------------
		
		var nPwrCbl = 0;
		var x = MM_RIGHTMARGIN;
		var y = TopMargin;
		
		// Draw iom's
		for (i=0; i<nb2608_0; i++)
			y+=CreateIom('2608-0',"16 Analog In",x,y,false,false);
	
		for (i=0; i<nb2608_4; i++)
			y+=CreateIom('2608-4',"16 AnalogIn/4 AnalogOut",x,y,false,false);
	
		for (i=0; i<nb2608_8; i++)
			y+=CreateIom('2608-8',"16 AnalogIn/8 AnalogOut",x,y,false,false);
	
		for (i=0; i<nb2612; i++)
			y+=CreateIom('2612','4 Strain Gauge',x,y,false,false);
	
		for (i=0; i<nb2620; i++)
			y+=CreateIom('2620','4 Counter/Timer/Encoder',x,y,false,false);
	
		for (i=0; i<nb2610; i++)
			y+=CreateIom('2610',"48 Digital I/O",x,y,true,nPwrCbl++==0);
	
		for (i=0; i<nb2650; i++)
			y+=CreateIom('2650',"8 Relay Out",x,y,true,nPwrCbl++==0);
	
		for (i=0; i<nb2653; i++)
			y+=CreateIom('2653',"16 SSR I/O",x,y,true,nPwrCbl++==0);
	
		for (i=0; i<nb2652; i++)
			y+=CreateIom('2652',"8 SSR I/O",x,y,true,nPwrCbl++==0);
	
		// Position 2601
		iomHeight2601 = nIoms*(IOM_HEIGHT+IOM_GAP);
		height2601 = iomHeight2601+(sios*SIO_HEIGHT);
		
		// Serial cables
		w=IOM_LEFTMARGIN+IOM_WIDTH+LEN_FIELDWIRE-MM_RIGHTMARGIN;
		y=TopMargin+iomHeight2601+SIO_HEIGHT/2;
		for (i=0; i<sios; i++, y+=SIO_HEIGHT)
			CreateFieldIo("RS-232/422/485","COM",MM_RIGHTMARGIN,y,w,"right");
		
		// Ethernet cable
		CreateFieldIo("LAN","Cat5",MM_LEFTMARGIN-ETH_CABLELEN,TopMargin+height2601/2,ETH_CABLELEN,"left");
		
		// Power supply
		y=TopMargin+height2601+PS_GAP;
		x=MM_LEFTMARGIN+MM_WIDTH/2;
		CreateFieldIo('120VAC','Line',MM_LEFTMARGIN-ETH_CABLELEN,y+IOM_HEIGHT/2,ETH_CABLELEN,"left");
		cable.push( new objCable('24VDC','right',x,TopMargin+height2601,x,y) );
		box.push( new objBox('204',MM_LEFTMARGIN,y,MM_WIDTH,IOM_HEIGHT) );
		
		// 2601
		box.push( new objBox('2601',MM_LEFTMARGIN,TopMargin,MM_WIDTH,height2601) );
		
		// Generate canvas objects in rendering order --------------------
	
		for (i=0; i<box.length; i++)
			box[i].drawShadow();
	
		for (i=0; i<cable.length; i++)
			cable[i].draw();
	
		for (i=0; i<box.length; i++)
			box[i].drawBox();
	
		for (i=0; i<label.length; i++)
			label[i].draw();

		// Render the graphics
		jg.paint();
	}
	
	DrawDiagram();
}

// Generate and render BOM.
function RenderBom()
{
	// Config constants -----------------------

	var URL_PREFIX = 'http://www.sensoray.com/products/';
	var COL_WIDTH = new Array( 88, 278, 32, 46, 68 );						// Widths of bom table columns, left to right.
	var BK_COLORS = new Array( 'rgb(255,255,255)', '#EFF0F1' );	// Alternating colors for body rows.
	var BORD_COLOR = 'rgb(232,232,240)';
	var HCELL_COLOR = 'rgb(72,79,94)';
	var HTEXT_COLOR = 'rgb(255,255,255)';
	
	// Storage -----------------------------------
	
	var bkColorIndex;	// current color ref (lsb)
	var totalPrice;		// system price
	
	// Local functions -----------------------------
	
	// Lookup price.
	function GetPrice( ProductId, Quantity )
	{
		var suffix;
		switch( Quantity )
		{
			case 0: return 0;
			case 1: suffix = "_a"; break;
			default: suffix = "_b"; break;
		}
		return parseInt( document.getElementById( "price_" + ProductId + suffix ).innerHTML );
	}

	// Generate cell style.
	function CellAttr( bkcolor )
	{
		return 'style="'+
			'BORDER-LEFT: ' + BORD_COLOR + ' 1pt solid;'+
			'BORDER-RIGHT: ' + BORD_COLOR + ' 1pt solid;'+
			'BORDER-TOP: ' + BORD_COLOR + ' 1pt solid;'+
			'BORDER-BOTTOM: ' + BORD_COLOR + ' 1pt solid;'+
			'PADDING-RIGHT: 3px;'+
			'PADDING-LEFT: 3px;'+
			'PADDING-BOTTOM: 1px;'+
			'PADDING-TOP: 1px;'+
			'BACKGROUND-COLOR: ' + bkcolor + '"';
	}
	
	// Generate heading cell.
	function BomHeadingCell( text )
	{
		return '<TD ' + CellAttr( HCELL_COLOR ) + ' height=16><P style="COLOR:' + HTEXT_COLOR + '"><B>' + text + '</B></P></TD>';
	}

	// Generate body cell.
	function BomBodyCell( textClass, bkcolor, text )
	{
		return '<TD ' + CellAttr( bkcolor ) + ' height=18><P class=' + textClass + '>' + text + '</P></TD>';
	}
		
	// Generate divider row.
	function BomDivider( gen, text )
	{
		if ( gen == 0 )
			return '';
			
		bkColorIndex = 0;	// init first row color

		return '<TR><TD ' + CellAttr( HCELL_COLOR ) + ' colSpan=5 height=16><P style="COLOR:' + HTEXT_COLOR + '"><B>' + text + '</B></P></TD></TR>';
	}

	// Generate bom body row, add extended price to total. Optional: modelRef, url.
	function BomBodyRow( modelRef, url, modelName, quantity, description )
	{
		// Skip row if zero items.
		if ( quantity == 0 )
			return '';
			
		// Set row color.
		var bkcolor = BK_COLORS[ bkColorIndex++ & 1 ];
		
		// If item not in price table ...
		var priceTxt,extTxt;
		if ( modelRef == '' )
			priceTxt = extTxt = 'note &sup2;';
		else
		{
			var price	= GetPrice( modelRef, quantity );
			var ext		= quantity * price;
			totalPrice	+= ext;
			priceTxt	= Formatted( price );
			extTxt		= Formatted( ext );
		}
		
		// Generate and return row html.
		return '<TR>' + BomBodyCell( 'bodytext', bkcolor, ( url == '' ) ? modelName :
			'<A href="' + URL_PREFIX + url + '"><SPAN style="COLOR: rgb(72,79,94)">' + modelName + '</SPAN></A>' ) +
			BomBodyCell( 'bodytext', bkcolor, description ) +
			BomBodyCell( 'numerical_right', bkcolor, quantity ) +
			BomBodyCell( 'numerical_right', bkcolor, priceTxt ) +
			BomBodyCell( 'numerical_right', bkcolor, extTxt ) + '</TR>';
	} 
	
	// Generate the bom html --------------------------
	
	totalPrice = 0;		// init system cost
	bkColorIndex = 0;	// init first row color

	// Generate and render bom.
	document.getElementById("bomcell").innerHTML =
	
		// Open table, declare column widths.
		'<TABLE cellSpacing=5 cellPadding=1 width="100%" bgColor=#ffffff border=0><COLGROUP><COL width=' + COL_WIDTH[0] + '><COL width=' + COL_WIDTH[1] + '><COL width=' + COL_WIDTH[2] + '><COL width=' + COL_WIDTH[3] + '><COL width=' + COL_WIDTH[4] + '></COLGROUP><TBODY>' +
	
		// Heading row.
		'<TR>'+BomHeadingCell( 'Model' )+
		BomHeadingCell( 'Description' )+
		BomHeadingCell( 'Qty.' )+
		BomHeadingCell( 'Ea. $' )+
		BomHeadingCell( 'Ext. $' )+'</TR>'+

		// Body rows.
		BomBodyRow( '2601', '2601.htm', '2601', nb2601, 'System hub, 10/100 Ethernet, 4 serial, interlock/power distr.' )+
		BomBodyRow( '2608_0', '2608.htm', '2608-0', nb2608_0, 'I/O module, 16 analog in, 0 analog out' )+
		BomBodyRow( '2608_4', '2608.htm', '2608-4', nb2608_4, 'I/O module, 16 analog in, 4 analog out' )+
		BomBodyRow( '2608_8', '2608.htm', '2608-8', nb2608_8, 'I/O module, 16 analog in, 8 analog out' )+
		BomBodyRow( '2610', '2610.htm', '2610', nb2610, 'I/O module, 48 digital in/out' )+
		BomBodyRow( '2612', '2612.htm', '2612', nb2612, 'I/O module, 4 strain gauge in' )+
		BomBodyRow( '2620', '2620.htm', '2620', nb2620, 'I/O module, 4 timer/counter w/quadrature input' )+
		BomBodyRow( '2650', '2650.htm', '2650', nb2650, 'I/O module, 8 mechanical relay' )+
		BomBodyRow( '2652', '2652.htm', '2652', nb2652, 'I/O module, 8 solid state relay' )+
		BomBodyRow( '2653', '2653.htm', '2653', nb2653, 'I/O module, 16 solid state relay' )+
		BomBodyRow( '204', '204.htm', '204', nbpwr, 'Power supply, 24V @ 5A' )+
		BomDivider( nbssr + rly120, 'Relays/SSRs' )+
		BomBodyRow( 'RELAY_IAC24', '', 'RELAY-IAC24', ssr120i, 'Solid state relay, 120V AC input' )+
		BomBodyRow( 'RELAY_IAC24A', '', 'RELAY-IAC24A', ssr240i, 'Solid state relay, 240V AC input' )+
		BomBodyRow( 'RELAY_IDC24', '', 'RELAY-IDC24', ssr32i, 'Solid state relay, 10 to 32V DC input' )+
		BomBodyRow( 'RELAY_OAC24', '', 'RELAY-OAC24', ssr120o, 'Solid state relay, 120V DC output' )+
		BomBodyRow( 'RELAY_OAC24A', '', 'RELAY-OAC24A', ssr240o, 'Solid state relay, 240V AC output' )+
		BomBodyRow( 'RELAY_ODC24', '', 'RELAY-ODC24', ssr60o, 'Solid state relay, 60V DC output' )+
		BomBodyRow( 'RELAY_ODC24R', '', 'RELAY-ODC24R', rly120, 'Mechanical relay, 120VAC, fits SSR socket' )+
		BomDivider( nb2600c1 + ncat5, 'Cables' )+
		BomBodyRow( '2600c1', '', '2600C1', nb2600c1, 'Daisy Chain power cable' )+
		BomBodyRow( '', '', '2601C1', ncat5, 'Cat 5 patch cable' )+
	
		// Total Cost row.
		'<TR><TD ' + CellAttr( HCELL_COLOR ) + ' colSpan=4 height=16><P class=bodytext style="TEXT-ALIGN:right;COLOR:' + HTEXT_COLOR + '"><B>Total : </B></P></TD><TD ' + CellAttr( 'rgb(255,255,255)' ) + '><P class=numerical_right>' + Formatted( totalPrice ) + '</P></TD></TR>'+
	
		// Close table.
		'</TBODY></TABLE>';
}

BuildSystem();

