'************************************************************************************************
'* Program:	PPM Production Performance Monitoring Kit
'* Date: 	07/30/2021
'* Description:	Production Performance Monitoring Kit Application Guide. For use with wireless Q45 Photoelectric sensors or TL70 wireless tower light nodes
'* Version: 1.1.0
'*
'* CHANGE LOG
'*	02/16/2021	C. Olivares
'*				Start Project
'*  08/30/2021 C. Olivares
'*              Removed syntax error on line 708
'* Copyright © 2020 Banner Engineering Corp
'* All rights reserved.
'*************************************************************************************************

'Script constants
CONST GWSID			= 1
CONST LocalReg		= 199
Const HoldingReg 	= 0
CONST DXM7IOSID   	= 203
CONST DXM10IOSID  	= 200
DXMIOID      = 0

CONST DXM7_MODELNUM = "203738*"
CONST DXM10_MODELNUM = "191897*"
CONST DXM12_MODELNUM = "212632*"

CONST DXM7IO_ADDR  = 2101
CONST DXM10IO_ADDR = 501
DXMIO_ADDR = 0

CONST DisplayReg		= 201
CONST DXM7Led_ADR       = 1107
CONST DXM10Led_ADR		= 1102
CONST DXM12Led_ADR      = 1103
DXMLED_ADR  = 0

'Control codes for writing TDMA parameter in Gateway
CONST RdControlCode	= 0xB320
CONST WrControlCode	= 0x9320
CONST ResetCode		= 0x0100
CONST GW_IO7		= 7
CONST GW_IO15		= 15
CONST GW_IO16		= 16
CONST TDMA47		= 0x0053
CONST RESETVALUE    = 5439
CONST DISPOFF_ADR_WR= 0x922A
CONST DISPOFF_ADR_RD= 0xB22A
'Control codes for report rate Write
CONST IO1_WR    =   0x8100
CONST IO2_WR    =   0x8200
CONST IO3_WR    =   0x8300
CONST IO5_WR    =   0x8500
CONST IO1_RD    =   0xA100
CONST IO2_RD    =   0xA200
CONST IO3_RD    =   0xA300
CONST IO5_RD    =   0xA500
CONST reportInt =   0x0004

'Other Constants
CONST MAX_TICKS = 429496729
CONST nl = "\n\r"

'Alternate Modbus Register groups to read from the DXM
CONST Input1 = 2
CONST Input2 = 50
CONST Input3 = 98
CONST Input4 = 146
CONST Input5 = 194
CONST Input6 = 242
CONST Input7 = 290
CONST Input8 = 338
CONST Output1 = 386
CONST Output2 = 434
CONST Output3 = 482
CONST Output4 = 530
CONST Output5 = 578
CONST Output6 = 626
CONST Output7 = 674
CONST Output8 = 722

'Alternate Modbus Register groups to read from the gateway (analog 16 bit regs - starting node 1)
CONST DevIn1reg     = 6802
CONST DevIn2reg     = 6952
CONST DevIn3reg     = 7102
CONST DevIn4reg     = 7252
CONST DevIn5reg     = 7402
CONST DevIn6reg     = 7552
CONST DevIn7reg     = 7702
CONST DevIn8reg     = 7852
CONST DevOut1reg    = 8002
CONST DevOut2reg    = 8152
CONST DevOut3reg    = 8302
CONST DevOut4reg    = 8452
CONST DevOut5reg    = 8602
CONST DevOut6reg    = 8752
CONST DevOut7reg    = 8902
CONST DevOut8reg    = 9052

'States
CONST Idle     = 100
CONST Running = 200
CONST Slowed = 300
CONST Stopped  = 400

'States for cloud
CONST Idle_cloud = 0
CONST Running_cloud = 1
CONST Slowed_cloud = 2
CONST Stopped_cloud = 3

'Local Registers
CONST partCount_ADR           = 5001
CONST PPM_ADR                 = 5051
CONST avgPPM_ADR              = 5101
CONST ictDev_ADR              = 5151
CONST lastCount_ADR           = 5201

CONST syncStatus_ADR        = 5501
CONST runTime_ADR           = 5551
CONST slowedTime_ADR        = 5601
CONST stoppedTime_ADR       = 5651
CONST onTime_ADR            = 5701
CONST avgSlowTime_ADR       = 5751
CONST avgStopTime_ADR       = 5801
CONST slowCount_ADR         = 5851
CONST stopCount_ADR         = 5901

CONST state_ADR             = 5951
CONST running_stat_ADR      = 6001
CONST slowed_stat_ADR       = 6051
CONST stopped_stat_ADR      = 6101
CONST availability_ADR      = 6151
CONST performance_ADR       = 6201
CONST lowPerfAlert_ADR      = 6251
CONST critPerfAlert_ADR     = 6301

CONST clearIndividualCounts_ADR = 6901
CONST cloudState_ADR        = 6951

'Non-Volatile Settings Registers (multinode systems)
CONST reportInt_ARR_ADR         = 7001
CONST ictRate_ARR_ADR           = 7051
CONST stopRate_ARR_ADR          = 7101
CONST lowPerformance_ARR_ADR    = 7151
CONST critPerformance_ARR_ADR   = 7201

'Commands Registers
CONST Initialize_ADR 	= 821
CONST clearCounts_ADR   = 822
CONST syncNodes_ADR     = 823
CONST Num_Q45_ADR   = 852
CONST PushCount_ADR = 824

'Parameters Registers
CONST supervisoryDataReg    = 801 
CONST SETTINGS_ADR = 851
CONST varSettings_ADR = 852
CONST firstRun_ADR 		= 899

'Parameter Defaults
'sets the default number of nodes
'set to 4
Num_Q45		= 47

'Address and value of the first time register

CONST firstRun_VAL 		= 9714
CONST Initialize_VAL	= 0
CONST clearCounts_VAL   = 0
'Initialize Variables
elapsedTime         = 0
lastTime            = 0
reportTime          = 0         
Num_Q45		        = 0                    
idealCycleTime      = 0     
stopCycleTime       = 0  
slowAlarmTime       = 0    
stoppedAlarmTime    = 0    
delayMode           = 0
orGreen             = 0
orYellow            = 0
orRed               = 0
sumSlowTime         = 0
sumStopTime         = 0
avgSlowTotalTime    = 0
avgStopTotalTime    = 0
sumSlowCounters     = 0
sumStopCounters     = 0
stpMult             = 0
pushRate            = 0
pushEnable          = 0
pushMask            = 0
testMask            = 0
stopCycleTimeOld    = 0
idealCycleTimeOld   = 0

'Initialize Variable Arrays
Function initializeVars

    For x = 0 to Num_Q45-1
        statusReg[x]   = 0
        countHigh[x]    = 0
        countLow[x]     = 0
        reportTime_ARR[x]   = 60
        forceSamRep_ARR[x] = 4927
        reportInt_ARR[x]    = 0
        IO1_WR_ARR[x]   = 0
        IO3_WR_ARR[x]   = 0
        Q45ClearCMD[x]  = 5388
        P16ClearCMD[x]  = 5123
        pc1[x]  = 0
        pc1Old[x]    = 0
        avgPPM[x]    = 0
        samCount[x] = 1
        stpCount[x] = 0
        PPM[x]  = 0
        PPM_Pub[x] = 0
        syncCommand[x] = 0x0100
        zero[x]         = 0
        state [x]            = Idle
        runTime[x]              = 0
        slowedTime[x]         = 0
        slowedTime_ARR[x]    = 0
        stoppedTime[x]       = 0
        stoppedTime_ARR[x]   = 0
        stopRate_ARR[x]     = 0
        ictRate_ARR[x]  = 0
        alarmStateSL[x] = 0
        alarmStateST[x] = 0
        pc_pub[x] = 0
        running_stat[x] = 0
        slowed_stat[x] = 0
        stopped_stat[x] = 0
        ictDev[x]   = 0
        clearCount_ARR[x] = 0
        slowCounter[x] = 0
        stopCounter[x] = 0
        avgSlowTime_Val[x] = 0
        avgStopTime_Val[x] = 0
        onTime_Val[x] = 0
        lastCount[x]    = 0
        availability[x] = 0
        performance[x]  = 0
        lowPerformance_ARR[x] = 0
        critPerformance_ARR[x] = 0
        lowPerfAlert_ARR[x]    = 0
        critPerfAlert_ARR[x]    = 0
    Next x 
end function

''''''Set TDMA devices to 48 '''''''''
FUNCTION RdWrTDMA

    WrErr		= SETREG(GW_IO15, RdControlCode, GWSID, HoldingReg)
    SLEEP(1)
    RdData 	= GETREG(GW_IO16, GWSID, HoldingReg)
    ' PRINT "GET TDMA47 = ", RdData, nl
    '
	IF RdData <> TDMA47 THEN
        PRINT "SET TDMA47", nl
		WrErr 	= SETREG(GW_IO16, TDMA47, GWSID, HoldingReg)
		WrErr 	= SETREG(GW_IO15, WrControlCode, GWSID, HoldingReg)
		SLEEP(1)
		WrErr 	= SETREG(GW_IO15, ResetCode, GWSID, HoldingReg)
	END IF
		
END FUNCTION

''''''''''''setReportRate'''''''''''''
function setReportRate
    Local x
    if  reportTime = 0 then 
        RdErr = MULTIGETARR(reportInt_ARR,16,reportInt_ARR_ADR,Num_Q45,LocalReg)
        PRINT "Set Individual Report Intervals", nl
    else if reportTime <> 0 then
        for x = 0 to Num_Q45-1
            reportInt_ARR[x] = reportTime*16
        next x
        PRINT "Set Global Report Intervals", nl
    end if

    for x = 0 to Num_Q45-1
        IO1_WR_ARR[x]   = IO1_WR+reportInt
        IO3_WR_ARR[x]   = IO3_WR+reportInt
    next x 
    'Set report interval value
    WrErr = MULTISETARR(reportInt_ARR,1,DevOut8reg,Num_Q45,GWSID)
    PRINT "Set report interval", nl
    Sleep(1)
    'Send write code to write report interval
    WrErr = MULTISETARR(IO1_WR_ARR,1,DevOut7reg,Num_Q45,GWSID)
    PRINT "Set report interval command IO 1", nl
    Sleep(1)
    WrErr = MULTISETARR(IO3_WR_ARR,1,DevOut7reg,Num_Q45,GWSID)
    PRINT "Set report interval command IO 3", nl
    Sleep(1)
end function

''''''''''''''First Run''''''''''''''''''''
FUNCTION firstRun
    'Sets all settings to default values, local reg 899 to any value not 9714
    LOCAL x
    RdData 	= GETREG(firstRun_ADR, LocalReg, HoldingReg)
    if RdData <> firstRun_VAL then
        PRINT "running_ARR the script for the first time", nl
        PRINT "Setting up default values:", nl
		''Set default register values here
        PRINT "851 - reportTime = 60 seconds", nl
        WrErr = MBREGOUT(0,60)
        PRINT "852 - Num_Q45 = 47 nodes", nl
        WrErr = MBREGOUT(1,47)
        PRINT "853 - Ideal Cycle Time (PPM) = 15", nl
        WrErr = MBREGOUT(2,15)
        PRINT "854 - Stopped Line Rate (PPM) = 3", nl
        WrErr = MBREGOUT(3,3)
        PRINT "855 - Slow Alarm Time = 300 Seconds (5 Minutes)", nl
        WrErr = MBREGOUT(4,300)
        PRINT "856 - Stopped Alarm Time = 150 Seconds (2.5 Minutes)", nl
        WrErr = MBREGOUT(5,150)
        PRINT "857 - Stop Count = 3", nl
        WrErr = MBREGOUT(6,3)
        PRINT "858 - Delay Mode = 1 ", nl
        WrErr = MBREGOUT(7,1)
        PRINT "859 - Cloud Push Rate = 300", nl
        WrErr = MBREGOUT(8,300)
        PRINT "860 - Cloud Push Enable = 1", nl
        WrErr = MBREGOUT(9,1)
        PRINT "861 - Low Performance = 85", nl
        WrErr = MBREGOUT(10,85)
        PRINT "862 - Critical Performance = 50", nl
        WrErr = MBREGOUT(11, 50)
        WrErr = MULTISET(SETTINGS_ADR,12,LocalReg,HoldingReg)
        'Set all individual parameters registers to defaults
        For x = 0 to 46
            reportInt_ARR[x]    = 60
            ictRate_ARR[x]      = 15
            stopRate_ARR[x]     = 3
            lowPerformance_ARR[x] = 85
            critPerformance_ARR[x] = 50
        next X
        idealCycleTimeOld = 15
        stopCycleTimeOld  = 3
        lowPerformanceOld = 85
        critPerformanceOld = 50
        Num_Q45 = 47
        WrErr = MULTISETARR(reportInt_ARR,1,reportInt_ARR_ADR,Num_Q45,LocalReg)
        WrErr = MULTISETARR(ictRate_ARR,1,ictRate_ARR_ADR,Num_Q45,LocalReg)
        WrErr = MULTISETARR(stopRate_ARR,1,stopRate_ARR_ADR,Num_Q45,LocalReg)
        WrErr = MULTISETARR(lowPerformance_ARR,1,lowPerformance_ARR_ADR,Num_Q45,LocalReg)
        WrErr = MULTISETARR(critPerformance_ARR,1,critPerformance_ARR_ADR,Num_Q45,LocalReg)

        'Set first time register so it does not run again
        WrErr = SETREG(firstRun_ADR, firstRun_VAL, LocalReg, 0)        
        PRINT "Default values set!!", nl
    end if
END FUNCTION

'''''''''Global Settings'''''''''''''''''''
FUNCTION globalSet(globalSetting,globalSetting_ARR,globalSetting_ARR_ADR,oldSetting)
    local X
    if globalSetting <> 0 then
        for x = 0 to Num_Q45-1
            globalSetting_ARR[x] = globalSetting
        next X
        if globalSetting <> oldSetting then
            WrErr = MULTISETARR(globalSetting_ARR,1,globalSetting_ARR_ADR,Num_Q45,LocalReg)
            oldSetting = globalSetting
            PRINT "changed regs ", globalSetting_ARR_ADR , nl 
        end if
    else if globalSetting = 0 then
        RdErr = MULTIGETARR(globalSetting_ARR,1,globalSetting_ARR_ADR,Num_Q45,LocalReg)
    end if
END FUNCTION

''''''''''''readSettings'''''''''''''''''''
'XML Set to Group by Device IN/OUT
function readSettings
    'Read Config Values
    RdErr = MULTIGET(SETTINGS_ADR, 12, LocalReg,HoldingReg)
    reportTime              = MBREGIN(0)
    Num_Q45		            = MBREGIN(1)
    idealCycleTime          = MBREGIN(2)
    stopCycleTime           = MBREGIN(3)
    slowAlarmTime           = MBREGIN(4)
    stoppedAlarmTime        = MBREGIN(5)
    stpMult                 = MBREGIN(6)
    delayMode               = MBREGIN(7)
    pushRate                = MBREGIN(8)
    pushEnable              = MBREGIN(9)
    lowPerformance          = MBREGIN(10)
    critPerformance         = MBREGIN(11)

    'make sure no more than 47 total nodes, priority to Q45s
    If Num_Q45 >= 47 Then    
        Num_Q45 = 47
    end if
    pushMask = 0
    for x=1 to Num_Q45
        pushMask = pushMask + 2^(x-1)
    next x
    WrErr = SETREG(Num_Q45_ADR,Num_Q45,LocalReg,HoldingReg)
    initializeVars
    Sleep(5)
    setReportRate
    PRINT "readSettings Complete", nl
end function

''''''''''''''clearCounts''''''''''''''''''
function clearCounts
    'Local RdData
    RdData 	= GETREG(clearCounts_ADR, LocalReg, HoldingReg)
    If RdData <> clearCounts_VAL then
        'Clears count of Q45s
        WrErr = MULTISETARR(Q45ClearCMD,1,DevOut7reg,Num_Q45,GWSID)
        WrErr 	  = SETREG(clearCounts_ADR, clearCounts_VAL, LocalReg, HoldingReg)
        PRINT "Node Counts Cleared", nl

        'Clears all other counts/metrics
        for x = 0 to Num_Q45-1
            samCount[x] = 1
            stpCount[x] = 0
            PPM[x]  = 0
            avgPPM[x] = 0
            avgPPMOld[x] = 0 
            stoppedTime[x] = 0
            slowedTime[x] = 0
            runTime[x] = 0
            state[x] = Idle
            pc1[x] = 0
            pc1Old[x] = 0
            pc_pub[x] = 0
            running_stat[x] = 0
            slowed_stat[x] = 0
            stopped_stat[x] = 0
            ictDev[x]   = 0
            slowCounter[x] = 0
            stopCounter[x] = 0
            avgSlowTime_Val[x] = 0
            avgStopTime_Val[x] = 0
            onTime_Val[x] = 0
        next x 
        PRINT "All Counters/Metrics Cleared", nl
    end if

    'Clear individual counts
    RdErr = MULTIGETARR(clearCount_ARR,1,clearIndividualCounts_ADR,Num_Q45,LocalReg)
    For x = 0 to Num_Q45-1
        If clearCount_ARR[x] <> clearCounts_VAL then
            WrErr=SETREG(DevOut7reg+x,Q45ClearCMD,GWSID,HoldingReg)
            WrErr=SETREG(clearIndividualCounts_ADR+x, clearCounts_VAL,LocalReg,HoldingReg)
            samCount[x] = 1
            stpCount[x] = 0
            PPM[x]  = 0
            avgPPM[x] = 0
            avgPPMOld[x] = 0 
            stoppedTime[x] = 0
            slowedTime[x] = 0
            runTime[x] = 0
            state[x] = Idle
            pc1[x] = 0
            pc1Old[x] = 0
            pc_pub[x] = 0
            running_stat[x] = 0
            slowed_stat[x] = 0
            stopped_stat[x] = 0
            ictDev[x]   = 0
            slowCounter[x] = 0
            stopCounter[x] = 0
            avgSlowTime_Val[x] = 0
            avgStopTime_Val[x] = 0
            onTime_Val[x] = 0
            PRINT "Cleared Counts for", x ,  nl
        end if
    next x 
end function

'''''''''''''Initialize'''''''''''''''
FUNCTION Initialize
    'Reads all settings from device and sets report rates, clears all counters, local reg 898
    LOCAL RdData
    RdData 	= GETREG(Initialize_ADR, LocalReg, HoldingReg)
    if RdData <> Initialize_VAL then
        PRINT "Re-Initializing", nl
        readSettings
        WrErr = SETREG(clearCounts_ADR,1,LocalReg,HoldingReg)
        clearCounts
		'Set Initialize register so it does not run again
        WrErr 	  = SETREG(Initialize_ADR, Initialize_VAL, LocalReg, HoldingReg)        
        PRINT "Settings read and applied, Counters Cleared, Initialize Complete", nl
    end if
END FUNCTION

''''''''''stateMachine'''''''''''''''''
'Runs State Machine for each node''''
function stateMachine(k)
    LOCAL tempState, k 
    tempState = state[k]
    'PRINT "Stop Rate ", stopRate_ARR[k] , "ideal cycle time ", ictRate_ARR[k], nl 
    if state[k] = Idle then
        'OFFTime[k] = OFFTime[k] + elapsedTimeTICKS(OFFTime[k])
        'PRINT "OFFTime node ", k+1 , "time ", OFFTime[k], nl 
        IF PPM[k] <> 0 then
            state[k] = Running
        end if
    ' Running State is 200
    else if state[k] = Running then
        'PRINT "Node", k+1, "State ", State[k], "PPM ", PPM[k],  nl 
        'if PPM[k] > stopRate_ARR[k] AND PPM[k] < ictRate_ARR[k] then
        if PPM[k] < ictRate_ARR[k] then 
            state[k] = Slowed
            slowCounter[k] = slowCounter[k] + 1
            PRINT "1 Changed State of node ", k+1 , " to ", state[k] ,"and slow counter is ", slowCounter[x] , nl
        else if PPM[k] <=stopRate_ARR[k] AND stpCount[k] >= stpMult then
            state[k] = Stopped
            stopCounter[k] = stopCounter[k] + 1
            PRINT "2 Changed State of node ", k+1 , " to ", state[k] , "and stop counter is ", stopCounter[k] , nl
        end if
    ' Slowed State is 300
    else if state[k] = Slowed then
        'PRINT "Node", k+1, "State ", State[k], "PPM ", PPM[k],  nl 
        if PPM[k] <= stopRate_ARR[k] AND stpCount[k] >= stpMult then
            state[k] = Stopped
            stopCounter[k] = stopCounter[k] + 1
            PRINT "3 Changed State of node ", k+1 , " to ", state[k] ,"and stop counter is ", stopCounter[k] , nl
        else if PPM[k] >= ictRate_ARR[k] then
            state[k] = Running
            PRINT "4 Changed State of node ", k+1 , " to ", state[k] , nl
        end if
    ' Stopped State is 400
    else if state[k] = Stopped then
        'PRINT "Node", k+1, "State ", State[k], "PPM ", PPM[k],  nl 
        if PPM[k] >= ictRate_ARR[k] then
            state[k] = Running
            stpCount[k] = 0
            PRINT "5 Changed State of node ", k+1 , " to ", state[k] , nl
        'else if PPM[k] >= stopRate_ARR[k] then
        else if PPM[k] >= stopRate_ARR[k] then 
            state[k] = Slowed
            slowCounter[k] = slowCounter[k] + 1
            stpCount[k] = 0
            PRINT "6 Changed State of node ", k+1 , " to ", state[k] , "and slow counter is ", slowCounter[x] , nl
        end if
    else 
        state[k] = Idle
    end if
    
    'Set timers and status regs
    if state[k] = Running then
        runTime[k] = runTime[k] + elapsedTime
        running_stat[k] = 1
        slowed_stat[k] = 0
        stopped_stat[k] = 0
    else if state[k] = Slowed then
        slowedTime[k] = slowedTime[k] + elapsedTime
        running_stat[k] = 0
        slowed_stat[k] = 1
        stopped_stat[k] = 0
    else if state[k] = Stopped then
        stoppedTime[k] = stoppedTime[k] + elapsedTime
        running_stat[k] = 0
        slowed_stat[k] = 0
        stopped_stat[k] = 1
    end if 

    'Calculate Average slow/stop time
    avgSlowTime_Val[k] = (slowedTime[k]/slowCounter[k])
    avgStopTime_Val[k] = (stoppedTime[k]/stopCounter[k])
    onTime_Val[k] = (runTime[k] + slowedTime[k] + stoppedTime[k])
    'Calculate availability and performance
    availability[k] = (runTime[k]+slowedTime[k])/onTime_Val[k]
    performance[k]  = (pc_pub[k]/(onTime_Val[k]*0.01))/(ictRate_ARR[k]/60)
    'PRINT "n", k ," performance ", performance[k], " part count ", pc_pub[k], " on time ", onTime_Val[k]*(1/100), " ict rate ", ictRate_ARR[k]/60, nl 
    'Determine Performance Alerts
    If (performance[k]*100) >= lowPerformance_ARR[k] then
        lowPerfAlert_ARR[k] = 0
        critPerfAlert_ARR[k] = 0
    else if (performance[k]*100) < lowPerformance_ARR[k] AND (performance[k]*100) >=critPerformance_ARR[k] then
        lowPerfAlert_ARR[k] = 1
        critPerfAlert_ARR[k] = 0
    else if (performance[k]*100) <= critPerformance_ARR[k] then
        lowPerfAlert_ARR[k] = 0
        critPerfAlert_ARR[k] = 1
    end if
end function

''''''MULTIGETARR function''''''''''''
' MULTIGETARR (array, multiplication factor, start address, reg count, SID)
function MULTIGETARR(arr, factor, startReg, regCnt, SID)
    local x
	MULTIGETARR = MULTIGET(startReg, regCnt, SID, 0)
	For x=0 to regCnt-1
		arr[x] = MBREGIN(x)*factor
	Next x	

end function

''''''MULTISETARR function''''''''''''
' MULTISETARR (array, multiplication factor, start address, reg count, SID)
function MULTISETARR(arr, factor, startReg, regCnt, SID)
    local x
    For x = 0 to regCnt-1
        WrErr = MBREGOUT (x, arr[x]*factor)
    Next x
    MULTISETARR = MULTISET (startReg, regCnt, SID, 0)

end function

'''''''''''''''DXMType''''''''''''''''
function DXMType
	modelNum = API(113)

	If modelNum LIKE DXM10_MODELNUM = -1 then
		DXMIOID = DXM10IOSID
		DXMIO_ADDR = DXM10IO_ADDR
        DXMLED_ADR = DXM10Led_ADR
		PRINT "DXM1000 - Model Number ", modelNum, nl
	elseif modelNum LIKE DXM7_MODELNUM = -1 then
		DXMIOID = DXM7IOSID
		DXMIO_ADDR = DXM7IO_ADDR
        DXMLED_ADR = DXM7Led_ADR
		PRINT "DXM700 - Model Number ", modelNum, nl
	else
        DXMLED_ADR = DXM12Led_ADR
		PRINT "DXM1200 or Other - Model Number ", modelNum, nl
	end if
end function

'''''''''Elapsed Time'''''''''''''
FUNCTION elapsedTimeTICKS(startTime)
    if TICKS >= startTime then
        elapsedTimeTICKS = (TICKS-startTime)
    else
        elapsedTimeTICKS = (MAX_TICKS+TICKS-startTime)
    end if
end FUNCTION

FUNCTION elapsedTimeSec(startTime)
    elapsedTimeSec = elapsedTimeTICKS(startTime)/100
end FUNCTION

FUNCTION elapsedTimeMin(startTime)
    elapsedTimeMin = elapsedTimeTICKS(startTime)/6000
end FUNCTION

'''''''''''syncNodes''''''''''''''
function syncNodes
    LOCAL RdData
    RdData = GETREG(syncNodes_ADR,LocalReg,HoldingReg)
    if RdData<>0 then
        PRINT "Syncing Nodes...", nl
        MULTISETARR(zero,1,DevIn8reg,Num_Q45,GWSID)
        sleep(1)
        MULTISETARR(syncCommand,1,DevOut7reg,Num_Q45,GWSID)
        WrErr = SETREG(syncNodes_ADR,0,LocalReg,HoldingReg)
        PRINT "sync complete ", nl
        Sleep (1)
        WrErr = SETREG(clearCounts_ADR,1,LocalReg,HoldingReg)
        clearCounts
    end if   
end function

''''''''''''readData'''''''''''''''''
function readData
    'Read Variable Setting Values
    RdErr = MULTIGET(varSettings_ADR, 11, LocalReg,HoldingReg)
    Num_Q45             = MBREGIN(0)
    idealCycleTime      = MBREGIN(1)
    stopCycleTime	    = MBREGIN(2)
    slowAlarmTime       = MBREGIN(3)
    stoppedAlarmTime    = MBREGIN(4)
    stpMult             = MBREGIN(5)
    delayMode           = MBREGIN(6)
    pushRate            = MBREGIN(7)
    pushEnable          = MBREGIN(8)
    lowPerformance      = MBREGIN(9)
    critPerformance     = MBREGIN(10)
    If Num_Q45 >= 47 Then    
        Num_Q45 = 47
    end if
    pushMask = 0
    for x=1 to Num_Q45
        pushMask = pushMask + 2^(x-1)
    next x
    WrErr = SETREG(Num_Q45_ADR,Num_Q45,LocalReg,HoldingReg)
    If idealCycleTime <> 0 Then
        if idealCycleTime <= stopCycleTime then
            stopCycleTime = idealCycleTime - 1
        end if
    end if

    if lowPerformance <> 0 then
        if lowPerformance <= critPerformance then
            critPerformance = lowPerformance - 5
        end if
    end if

    'Read Status Register
    RdErr = MULTIGETARR(statusReg,1,Input8,Num_Q45,LocalReg)
    'Set out of sync condition
    For x = 0 to Num_Q45-1
        if statusReg[x] <> 128 then
            syncStatus[x] = 0
        else if statusReg[x] = 128 then
            syncStatus[x] = 128
        end if
    next x

    WrErr = globalSet(idealCycleTime,ictRate_ARR,ictRate_ARR_ADR,idealCycleTimeOld)
    WrErr = globalSet(stopCycleTime,stopRate_ARR,stopRate_ARR_ADR,stopCycleTimeOld)
    WrErr = globalSet(lowPerformance,lowPerformance_ARR,lowPerformance_ARR_ADR,lowPerformanceOld)
    WrErr = globalSet(critPerformance,critPerformance_ARR,critPerformance_ARR_ADR,critPerformanceOld)

    'MULTIGETARR (array, multiplication factor, start address, reg count, SID)
    'Read Sensor Count High Word
    RdErr = MULTIGETARR(countHigh,1,Input3,Num_Q45,LocalReg)
  
    'Read Sensor Count Low Word
    RdErr = MULTIGETARR(countLow,1,Input4,Num_Q45,LocalReg)
    'PRINT "count high", countHigh[Num_Q45-1], "count low", countLow[Num_Q45-1], nl
end function

''''''''''''partsCounting''''''''''''''''''
function partsCounting
    'Check if the part count has updated
    For x = 0 to Num_Q45-1
        'First calculate all counter 1 part counts
        pc1[x] = (countHigh[x]*65536)+countLow[x]
        'PRINT "node " , x+1 ," PC ", pc1[x] ,nl
        if pc1[x] <> 0 and syncStatus[x] = 128 then
            PRINT "Begin Analysis...", x+1 , nl
            'Calculate PPM
            if pc1[x] <> pc1Old[x] then
                PPM[x] = ((pc1[x] - pc1Old[x])/(reportInt_ARR[x]/(16*60)))
                avgPPM[x] = avgPPM[x] + (PPM[x]-avgPPM[x])/samCount[x]
                samCount[x] = samCount[x] + 1
                lastCount[x] = pc1[x]-pc1Old[x]
                pc1Old[x] = pc1[x]
                ictDev[x] = (avgPPM[x] - ictRate_ARR[x])/ictRate_ARR[x] * 100
                stpCount[x] = 0
                PRINT "enter 1 ", x+1 ," PPM ", PPM[x]  , "Avg PPM ", avgPPM[x] , "samCount ", samCount[x],"part count", pc1[x], "old pc ", pc1Old[x], " last count ", lastCount[x], nl 
            else if pc1[x] = pc1Old[x] then
                PPM[x] = 0
                lastCount[x] = 0
                stpCount[x] = stpCount[x] + 1
                PRINT "enter 2 ", x+1 ," PPM ", PPM[x]  , "Avg PPM ", avgPPM[x] , "samCount ", samCount[x],"stpCount ", stpCount[x] , "old pc ", pc1Old[x], nl
            end if
            pc_pub[x] = pc1[x]
            'Sets registers back to 0
            WrErr = SETREG(Input3+X,0,LocalReg,HoldingReg)
            WrErr = SETREG(Input4+X,0,LocalReg,HoldingReg)
           'Compare to Stopped State  
        end if
    next x
end function

''''''''''''Main Loop''''''''''''''''''
function main
    'Get time since last run for status values
    'Check for roll over
    if lastTime > TICKS then
        elapsedTime = TICKS + MAX_TICKS-lastTime
    else
        elapsedTime = TICKS - lastTime
    end if
    lastTime = TICKS

    readData
    partsCounting
    for x = 0 to Num_Q45-1
        if syncStatus[x] = 128 then   
            stateMachine(x)
        end if
    Next x 
    'Supervisory Data, tower light states, and cloud status regs
    orGreen = 0
    orYellow = 0
    orRed = 0
    sumSlowCounters = 0
    sumStopCounters = 0
    sumSlowTime = 0
    sumStopTime = 0
    orLowPerf   = 0
    orCritPerf  = 0
    for x = 0 to Num_Q45-1
        orGreen = orGreen OR running_stat[x]
        orYellow = orYellow OR slowed_stat[x]
        orRed = orRed OR stopped_stat[x]
        sumSlowCounters = sumSlowCounters + slowCounter[x]
        sumStopCounters = sumStopCounters + stopCounter[x]
        sumSlowTime     = sumSlowTime + slowedTime[x]
        sumStopTime     = sumStopTime + stoppedTime[x]
        if state[x] = Idle then
            cloudState[x] = Idle_cloud
        else if state[x] = Running then
            cloudState[x] = Running_cloud
        else if state[x] = Slowed then
            cloudState[x] = Slowed_cloud
        else if state[x] = Stopped then
            cloudState[x]  = Stopped_cloud
        end if
        orLowPerf = orLowPerf OR lowPerfAlert_ARR[x]
        orCritPerf = orCritPerf OR critPerfAlert_ARR[x]
    next x
    avgSlowTotalTime = sumSlowTime/sumSlowCounters
    avgStopTotalTime = sumStopTime/sumStopCounters

end function

'''''''''''writeData'''''''''''''''''''
function writeData
    'MULTISETARR (array, multiplication factor, start address, reg count, SID)
    'write supervisory data
    Result = MBREGOUT(0,orGreen)
    Result = MBREGOUT(1,orYellow)
    Result = MBREGOUT(2,orRed)
    Result = MBREGOUT(3,sumSlowCounters)
    Result = MBREGOUT(4,sumStopCounters)
    Result = MBREGOUT(5,avgSlowTotalTime/100)
    Result = MBREGOUT(6,avgStopTotalTime/100)
    Result = MBREGOUT(7,sumSlowTime/100)
    Result = MBREGOUT(8,sumStopTime/100)
    Result = MBREGOUT(9,orLowPerf)
    Result = MBREGOUT(10,orCritPerf)
    WrErr = MULTISET(supervisoryDataReg,11,LocalReg,HoldingReg)
    'write part in counts
    WrErr = MULTISETARR(pc_pub,1,partCount_ADR,Num_Q45,LocalReg)
    'Write parts per minute
    WrErr = MULTISETARR(PPM,1,PPM_ADR,Num_Q45,LocalReg)
    'Write %Deviation of ICT
    WrErr = MULTISETARR(ictDev,1,ictDev_ADR,Num_Q45,LocalReg)
    'Write average throughput
    WrErr = MULTISETARR(avgPPM,1,avgPPM_ADR,Num_Q45,LocalReg)
    'Write out of sync alarm state 
    WrErr = MULTISETARR(syncStatus,1,syncStatus_ADR,Num_Q45,LocalReg)
    'Write Last part count
    WrErr = MULTISETARR(lastCount,1,lastCount_ADR,Num_Q45,LocalReg)
    'Write performance and availability and alerts
    WrErr = MULTISETARR(performance,10000,performance_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(availability,10000,availability_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(lowPerfAlert_ARR,1,lowPerfAlert_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(critPerfAlert_ARR,1,critPerfAlert_ADR,Num_Q45,LocalReg)
    'Write out timers
    WrErr = MULTISETARR(runTime,1/100,runTime_ADR, Num_Q45,LocalReg)
    WrErr = MULTISETARR(slowedTime,1/100,slowedTime_ADR,Num_Q45, LocalReg)
    WrErr = MULTISETARR(stoppedTime,1/100,stoppedTime_ADR,Num_Q45, LocalReg)
    WrErr = MULTISETARR(State,1,state_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(cloudState,1,cloudState_ADR,Num_Q45,LocalReg)
    'Write Run flags for individual sensors to local regs and to node outputs 
    WrErr = MULTISETARR(running_stat,1,running_stat_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(slowed_stat,1,slowed_stat_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(stopped_stat,1,stopped_stat_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(running_stat,1,Output1,Num_Q45,LocalReg)
    WrErr = MULTISETARR(slowed_stat,1,Output2,Num_Q45,LocalReg)
    WrErr = MULTISETARR(stopped_stat,1,Output3,Num_Q45,LocalReg)
    'Write slow/stop counts
    WrErr = MULTISETARR(slowCounter,1,slowCount_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(stopCounter,1,stopCount_ADR,Num_Q45,LocalReg)
    'Write average slow/stop time and total ON time
    WrErr = MULTISETARR(avgSlowTime_Val,1/100,avgSlowTime_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(avgStopTime_Val,1/100,avgStopTime_ADR,Num_Q45,LocalReg)
    WrErr = MULTISETARR(onTime_Val,1/100,onTime_ADR,Num_Q45,LocalReg)
end function

Function PushGroups
    RdData = GETREG(PushCount_ADR, LocalReg,HoldingReg)
    If RdData >= pushRate AND pushEnable <>0 then
        PRINT "Pushing data - pushMask: 0x", HEX(pushMask),nl
        ' Push mask needs to be a string, thus the concatenation below
        testMask = "0" & (HEX(pushMask))
        PRINT "testMask:  ", testMask, nl
        ' Push with the mask of devices in the network
        WrErr = API(6, testMask) 
        PRINT "WrErr: ", WrErr,nl
        WrErr = SETREG(PushCount_ADR,0,LocalReg,HoldingReg)
    end if
End Function

'Initializes
RdWrTDMA
DXMType
firstRun
'wait for the nodes to come online
status = 0
WHILE(status = 0)
    PRINT "Waiting for the nodes to come online...", nl
    RdErr 	  = MULTIGET(6601, 3, GWSID, 0)
    status = (MBREGIN(0) AND 65534) + MBREGIN(1) + MBREGIN(2)
    SLEEP(2)
WEND
PRINT "Starting...", nl
'Read DXM Settings
ReadSettings
'sync nodes to get status reg
Sleep(5) 
WrErr = SETREG(syncNodes_ADR, 1, LocalReg,HoldingReg)
syncNodes
'Clear Counts
WrErr = SETREG(clearCounts_ADR,1,LocalReg, HoldingReg)
Sleep(2)
clearCounts

While (1)
    script_start_time = TICKS
	ScriptIsRunning = SETREG(DXMLED_ADR, 1, DisplayReg,HoldingReg)
	Initialize
    syncNodes
	main
	writeData
	clearCounts
    PushGroups 
    'Delay Mode allows for easier manual debug of the system.
    IF delayMode <> 0 THEN
        SLEEP(2)
    end if

    script_end_time = TICKS
	script_single_scan = (script_end_time - script_start_time) * 10
	PRINT "Script run time is ", script_single_scan," ms.\r\n"
Wend