Stylesheet ../dokuwiki/css/_search.css not found, using ../dokuwiki/css/_search.less instead. Please contact developer of "altronic" template.
Stylesheet ../dokuwiki/css/_admin.css not found, using ../dokuwiki/css/_admin.less instead. Please contact developer of "altronic" template.

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
documents:de4000:de4000script [2021/12/23 12:24]
admin
documents:de4000:de4000script [2022/07/08 13:11] (current)
rjackson
Line 1: Line 1:
-<html> +=====DE-4000 SCRIPTING REFERENCE MANUAL===== 
-<body+ 
-This is html +There is a delicate balance between providing a system that has capabilities that can be configured through a fixed set of options, and one that can be extended and expanded with custom programming. In designing the DE-4000 control system, the choice was made to provide a system where most applications can be met with simple configuration, but advanced functionality can be provided through custom programming using the "Lua" language. 
-</body+ 
-</html>+Lua is often referred to as a scripting language. Scripting languages differ from compiled languages as they eliminate extra step of compiling the written program into machine code. 
 +  
 +Lua comes with a background of being robust, fast, and geared towards embedded 
 +applications, with a proven track record in the gaming industry. For the DE-4000 
 +system it is small and fits in the memory we have available, holds a lot of power, and 
 +keeps it simple for writing in the language. 
 +All information regarding the Lua scripting language is located at https://Lua.org 
 +Using the Lua engine as an embedded tool allows for taking advantage of a full 
 +architecture and standard at your fingertips. Within the language there are all of 
 +the normal attributes to programming such as functions, variables, statements, 
 +expressions etc. All of this reference material can be found at https://lua.org/ 
 +manual/5.3/ 
 +For getting started and using a guided reference, there are several editions of 
 +“Programming in Lua” available. Most recent editions are a paid for product that 
 +come in paper back or ebook form. While testing out Lua and becoming familiar, a 
 +free first edition is available and covers a lot of learning needs to get comfortable with 
 +the language. It can be located at https://www.lua.org/pil/contents.html
 +A major advantage to using Lua is its inherent ability to allow custom functions. While 
 +all normal functions and calls are published, there is the ability to add new functions 
 +in the DE-4000 firmware. Once new functions are defined and have calls to their 
 +internal properties, they then can be published for the user. This includes functions 
 +such as our flexible Modbus table and talking with various terminal boards linked in 
 +the system. Below is the start to the list of Altronic based functions. As functionality 
 +and features come to life through new ideas, this document will continually get 
 +updated with the latest scripts that we make available. 
 + 
 + 
 +GETTING STARTED WITH DE-4000 SCRIPTS 
 +Basic Scripting on DE-4000 
 + 
 +===== - Begin on Dashboard on DE-4000 system environment ===== 
 +{{:documents:de4000:scripting_dashboard_1.jpg?400|}}  
 + 
 + 
 +===== - Choose “Global” from menu on left side of screen ===== 
 +{{:documents:de4000:scripting_dashboard_2.jpg?400|}} 
 + 
 + 
 +===== - In the Sub-Menu on the Left side select “Scripts” ===== 
 +{{:documents:de4000:scripting_dashboard_3.jpg?400|}} 
 + 
 + 
 +===== - Select one of the page icons under one of the 4 script options to open editor ===== 
 +{{:documents:de4000:scripting_dashboard_4.jpg?400|}} 
 + 
 +===== -  Scripting can be entered into the editor ===== 
 +{{:documents:de4000:scripting_dashboard_5.jpg?400|}} 
 + 
 + 
 +**Scripting Windows and examples** 
 + 
 +**Master Script** 
 +{{:documents:de4000:scripting_dashboard_master_script.jpg?90|}}  The Master Script section is the Primary scripting environment. 
 +Primary scripting functions can be written in this section. 
 + 
 +**Example:** 
 +<code lua> 
 +local suction = get_channel_val(1,1) 
 +local discharge1 = get_channel_val(1,3) 
 +diff = discharge1 - suction 
 +set_sVirt(“Difference”, diff) 
 +</code
 + 
 +The first line gets the channel value from Terminal board 1 Input 1 and stores it 
 +in local variable named suction. 
 +The second line gets the channel value from Terminal board 1 Input 3 and stores 
 +it in local variable named discharge1. 
 +The third line takes the discharge1 pressure and subtracts the suction pressure 
 +and stores it in the global variable named diff (NOTE: Any value that you want to 
 +access from another scripting section must be stored in a global variable. This is 
 +used most in calling values into Modbus registers as explained below). 
 +The fourth line copies the value from diff and stores it into the Virtual status 
 +channel named “Difference” This channel can be displayed on the Dashboard. 
 + 
 +**Control Script** 
 +{{:documents:de4000:scripting_dashboard_control_script.jpg?90|}} 
 +The Control Script section is used to override the default control 
 +strategy found on the Global/Control page. A copy of the default 
 +control script (found in attached appendix) can be copied into this 
 +section and then modified to change the control functionality as well 
 +as add additional control loops beyond the default 2. 
 + 
 +{{:documents:de4000:scripting_dashboard_control_script_2.jpg?400|}} 
 + 
 +**Modbus Script** 
 +{{:documents:de4000:scripting_dashboard_modbus_script.jpg?90|}} 
 +The Modbus Script section is used to move data into and out of 
 +Modbus registers 
 + 
 +{{:documents:de4000:scripting_dashboard_modbus_script_2.jpg?400|}} 
 + 
 +<code lua> 
 +defaultModbus() 
 +set_modbus(300,diff) 
 +</code> 
 + 
 +The first line pulls in the factory set Modbus mapping 
 +The second line moves the value from the global variable named diff into the 
 +40300 Modbus Register 
 + 
 +==== - DE-4000 Lua Script API ==== 
 +<markdown> 
 +**CUSTOM FUNCTIONS FOR SCRIPTING** 
 + 
 +--- 
 +##### create_param("index",default,"catergory","description"
 +* creates a user configurable parameter   
 +* parameter is stored as `index`, 
 +* default value(If not changed by user) is `default`   
 +* parameters will be grouped on the Global/Params page by `category`   
 +* `description` is text to describe the parameter to the user 
 + 
 +**Example:**   
 +```lua 
 +create_param("NumEngCyl",8,"Engine Params","Num. of Engine Cylinders"
 +``` 
 +--- 
 +##### get_channel_val(terminal,channel) 
 +* returns current value of analog input `channel` on terminal module `terminal` 
 +* return value type is numeric 
 + 
 +**Example:**  
 +```lua 
 +local sp = get_channel_val(1,5) 
 +``` 
 + 
 +> reads value of Suction Pressure  from Terminal Module #1 , Input #5 
 + 
 +--- 
 + 
 +##### get_gbl("index",default) 
 +* returns global config setting stored under `index` or returns `default` if not defined 
 +> **note:** get_gbl is used to retrieve global CONFIGURATION settings that are typically set when the system is configured and do not change as the system is running. 
 +> If you want to set and retrieve global STATUS variables use the get_sGbl() and set_sGbl() functions 
 +>If you want to create and read virtual channels use the set_sVirt() and get_sVirt() functions. 
 + 
 +**Example:** 
 +```lua 
 +local nt = get_gbl("NumTerm",1) 
 +``` 
 + 
 +> gets the number of terminal boards installed in the system 
 + 
 +--- 
 + 
 +##### get_param("index"
 +* return either the default value or the user configured value of the parameter `index` 
 + 
 +**Example:** 
 + 
 +```lua 
 +get_param("NumEngCyl"
 +``` 
 + 
 +>gets the configured parameter for number of engine cylinders 
 + 
 +--- 
 + 
 +##### get_rpm(channel) 
 +* reads the RPM input `channel` in units of revolutions per minute 
 + 
 +> **note:** valid channel numbers are 1 - 10(2 channels per board, up to 5 terminal boards) 
 + 
 +Each Terminal Module has 2 RPM inputs (RPM1 and RPM2) 
 +* Terminal Module **#1** RPM channels are **1,2** 
 +* Terminal Module **#2** RPM channels are **3,4** 
 +* Terminal Module **#3** RPM channels are **5,6** 
 +* Terminal Module **#4** RPM channels are **7,8** 
 +* Terminal Module **#5** RPM channels are **9,10** 
 + 
 +**Example:** 
 + 
 +```lua 
 +local engineRPM = get_rpm(1) 
 +local turboRPM = get_rpm(6) 
 +``` 
 + 
 +> Read RPM1 channel from terminal module #1 and read RPM2 channel from Terminal module #3 
 + 
 +--- 
 + 
 + 
 +##### get_sGbl("index", default) 
 + 
 +* If `index` is defined in the global status table then it returns the value associated with `index` 
 +*  If `index` is not defined and optional `default` is provided then returns `default` 
 + 
 +>**note:** It is recommended to always provide a default value when using this function 
 + 
 +**Example:** 
 +```lua 
 +local cp = get_sGbl("calculatedPressure",0) 
 +``` 
 + 
 +> get the previously stored value "calculatedPressure", Returns `0` if not found. 
 + 
 +--- 
 + 
 +##### get_state() 
 + 
 +* returns the current engine state(possible values currently 0 - 10) 
 + 
 +**Example:** 
 +```lua 
 +local engineState = get_state() 
 +if engineState > 7 then 
 +    set_timer("WarmupTimer",1000) 
 +end 
 +``` 
 + 
 +--- 
 + 
 +##### get_sVirt("index"
 + 
 +* returns the value of virtual channel `index` or returns `default` if the virtual channel does not exist. 
 + 
 +**Example:** 
 + 
 +```lua 
 +local tl = get_sGbl("timeLimit"
 +local et = get_sVirt("ElapsedTime",0) 
 +if et > tl then 
 +    set_sGbl("timeExceeded",true) 
 +else 
 +    set_sGbl("timeExceeded",false) 
 +end 
 +``` 
 + 
 +>Gets the value of virtual channel ElapsedTime and set value of status global "timeExceeded" if ElapsedTime is greater than status global "timeLimit" 
 + 
 +--- 
 + 
 +##### get_time() 
 + 
 +* returns the UNIX "epoch" time (Defined as the number of seconds elapsed since Jan 1, 1970) 
 + 
 +**Example:** 
 + 
 +```lua 
 +local startTime = get_sGbl("startTime",0) 
 +if startTime == 0 then 
 +    local currentTime = get_time() 
 +    startTime = currentTime 
 +    set_sGbl("startTime",currentTime) 
 +end 
 +local et = get_time() - startTime 
 +set_sVirt("ElapsedTime",et) 
 +``` 
 + 
 +>Stores current time if first time through, otherwise calculate elapsed time 
 + 
 +--- 
 + 
 +##### get_timer("index"
 + 
 +* returns 1 or 2 values 
 +* First return value(Boolean) is **true** if timer is active(counting down) or **false** if timer is expired or has not been set yet 
 +* Second return value is the number of seconds remaining or **-1** if timer is not active or has not been set yet 
 + 
 +**Example:** 
 + 
 +```lua 
 +if not get_timer("myTimer") then 
 +    set_sGbl("timedOut",true) 
 +else 
 +    set_sGbl("timedOut",false) 
 +end 
 +``` 
 + 
 +> if timer is expired, then set global status "timedOut" to **true** 
 + 
 +```lue 
 +local active,remaining = get_timer("myTimer"
 +if not active then 
 +    set_sVirt("timeRemaining","Expired"
 +else 
 +    set_sVirt("timeRemaining",remaining) 
 +end 
 +``` 
 + 
 +--- 
 + 
 +##### getStateLabel(state) 
 + 
 +* return the label for the engine state corresponding to the parameter `state` 
 + 
 +**Example:** 
 + 
 +```lua 
 +local stateLabel = getStateLabel(get_state()) 
 +local active, remaining = get_timer("myTimer"
 +if remaining > 0 then 
 +    stateLabel == StateLabel.." "..remaining 
 +end 
 +set_sVirt("Countdown",stateLabel) 
 +``` 
 + 
 +--- 
 +##### set_sGbl("index",value) 
 + 
 +* store `value` in the global status table under `index` 
 +* value can be a number or string but if storing a boolean use the **tostring()** function 
 + 
 +**Example:** 
 + 
 +```lua 
 +local mpe = false 
 +local sp = get_channel_val(1,5) 
 +if sp > 15 then 
 +    mpe = true 
 +end 
 +set_sGbl("minPressureExceeded",tostring(mpe)) 
 +``` 
 + 
 +> store boolean value **minPressureExceeded** 
 + 
 +--- 
 + 
 +##### set_sVirt("index",value) 
 + 
 +* sets a virtual status channel with channel name `index` 
 +> **Note:** Once you create a virtual channel, you can add that channel to the dashboard using the channel name `index` 
 + 
 +**Example:** 
 + 
 +```lua 
 +local sp = get_channel_val(1,5) --suction pressure 
 +local dp = get_channel_val(1,6) --discharge pressure 
 +local diffPress = dp - sp 
 +set_sVirt("SuctDischDiff",diffPress) 
 +``` 
 + 
 +> calculate the differential between suction and discharge pressure and assign to virtual channel 
 + 
 +--- 
 + 
 +##### set_timer("index",secs) 
 + 
 +* activate timer `index` and set countdown time to `secs` 
 + 
 +**Example:** 
 + 
 +```lua 
 +set_timer("myTimer",300) 
 +``` 
 + 
 +> create timer `myTimer` and start countdown time to 300 seconds 
 + 
 +--- 
 + 
 + 
 +</markdown> 
 +==== - Master Control Script ==== 
 + 
 +{{ :documents:de4000:control.jpg?nolink&600 |}} 
 + 
 +When you enter a control setup under the Global Control page the code that runs is called MasterControl. 
 + 
 +If you wish to modify this functionality you can copy this code into the Control Script editor and{{ :documents:de4000:scripting_dashboard_control_script.jpg?nolink|}} make your changes to the standard configuration. 
 + 
 + 
 +<file lua [enable_line_numbers="true"] mastercontrol.lua> 
 +  local rampRate1 = get_gbl("rampRate1",0.8) 
 +  local rampRate2 = get_gbl("rampRate2",0.8) 
 +  local dischTerm = tonumber_def(get_gbl("spDischTerm",0),0) 
 +  local dischChan = tonumber_def(get_gbl("spDischChan",0),0) 
 +  local suctTerm = tonumber_def(get_gbl("spSuctTerm",0),0) 
 +  local suctChan = tonumber_def(get_gbl("spSuctChan",0),0) 
 +  local suctMin = tonumber_def(get_gbl("suctMin",0),0) 
 +  local recycleMin = tonumber_def(get_gbl("recycleMin",0),0) 
 +  local recycleMax = tonumber_def(get_gbl("recycleMax",0),0) 
 +  local suctSp = tonumber_def(get_gbl("suctSp",0),0) 
 +  local dischMax = tonumber_def(get_gbl("dischMax",0),0) 
 +  local dischSp = tonumber_def(get_gbl("dischSp",0),0) 
 +  local outputTerm = tonumber_def(get_gbl("outputTerm",0),0) 
 +  local outputChan = tonumber_def(get_gbl("outputChan",0),0) 
 +  local recycleTerm = tonumber_def(get_gbl("outputTerm2",0),0) 
 +  local recycleChan = tonumber_def(get_gbl("outputChan2",0),0) 
 +  local speedRevAct = tonumber_def(get_gbl("speedRevAct",0),0) 
 +  local recycleRevAct = tonumber_def(get_gbl("recycleRevAct",0),0) 
 +  local outputLow = tonumber_def(get_gbl("outputLow",0),0) 
 +  local outputLow2 = tonumber_def(get_gbl("outputLow2",0),0) 
 +  local outputHigh = tonumber_def(get_gbl("outputHigh",0),0) 
 +  local outputHigh2 = tonumber_def(get_gbl("outputHigh2",0),0) 
 +  local spSuctType = get_gbl("spSuctType","linear"
 +  local spDischType = get_gbl("spDischType","linear"
 +  local suctPIDPFactor = tonumber_def(get_gbl("suctPIDPFactor",0),0) 
 +  local suctPIDIFactor = tonumber_def(get_gbl("suctPIDIFactor",0),0) 
 +  local suctPIDDFactor = tonumber_def(get_gbl("suctPIDDFactor",0),0) 
 +  local dischPIDPFactor = tonumber_def(get_gbl("dischPIDPFactor",0),0) 
 +  local dischPIDIFactor = tonumber_def(get_gbl("dischPIDIFactor",0),0) 
 +  local dischPIDDFactor = tonumber_def(get_gbl("dischPIDDFactor",0),0) 
 +  local recycleCtrl = false 
 +  local recycleSuctionRev = false 
 +  local recycleDischargeRev = false 
 +  if recycleChan > 0 and recycleTerm > 0 then 
 +    recycleCtrl = true 
 +  end 
 +  
 +  local dischPct = 100 
 +  local suctPct = 100 
 + 
 + 
 +  local dischOutput = 0 
 +  local suctOutput = 0 
 +  local rSuctOutput = 0 
 +  local rDischOutput = 0 
 +  local minLoad = 0 
 +  local maxLoad = 100 
 +  local minRecycle = 0 
 +  local maxRecycle = 100 
 +  local speedTarget = get_sGbl("speedTarget",0) 
 +  local recycleTarget = get_sGbl("recycleTarget",0) 
 + 
 +  function map_range(rangeLow,rangeHigh,input) 
 +    if input <= rangeLow and input <= rangeHigh then 
 +      return 0 
 +    end 
 +    if input >= rangeLow and input >= rangeHigh then 
 +      return 100 
 +    end 
 +    local rangeDiff = math.abs(rangeLow - rangeHigh) 
 +    local min = math.min(rangeLow,rangeHigh) 
 +    local retval = math.abs(input - min) / rangeDiff * 100 
 +    if retval > 100 then retval = 100 end 
 +    if retval < 0 then retval = 0 end 
 +    return retval 
 +  end 
 + 
 +  local suct = false 
 +  local suctVal = 0 
 +  if tonumber_def(get_gbl("spSuctEn",0),0) == 1 then 
 +    if suctTerm > 0 and suctChan > 0 then 
 +      suctVal = get_channel_val(suctTerm,suctChan) 
 +      suct = true 
 +    end 
 +  end 
 + 
 + 
 +  if suct then 
 +    if spSuctType == "linear" then 
 +      local suctDiff = suctSp - suctMin 
 +      if suctDiff == 0 then suctDiff = 1 end 
 +      if suctVal < suctSp then 
 +        local suctErr = suctSp - suctVal 
 +        suctPct = suctErr / suctDiff 
 +        if suctPct > 1 then suctPct = 1 end 
 +        if suctPct < 0 then suctPct = 0 end 
 +        suctOutput = (1 - suctPct) * 100 
 +      else 
 +        suctOutput = 100 
 +      end 
 +    else 
 +      set_gbl("PIDsuctEnable",1) 
 +      set_gbl("PIDsuctPFactor",suctPIDPFactor) 
 +      set_gbl("PIDsuctIFactor",suctPIDIFactor) 
 +      set_gbl("PIDsuctDFactor",suctPIDDFactor) 
 +      set_gbl("PIDsuctSp",suctSp) 
 +      set_gbl("PIDsuctDeadband",0.2) 
 +      local suctPidOutput = doPid("suct",suctVal) 
 +      suctOutput = suctPidOutput 
 +    end 
 +  else 
 +    suctOutput = 100 
 +  end 
 + 
 + 
 +  local disch = false 
 +  local dischVal = 0 
 +  if tonumber_def(get_gbl("spDischEn",0),0) == 1 then 
 +    if dischTerm > 0 and dischChan > 0 then 
 +        dischVal = get_channel_val(dischTerm,dischChan) 
 +        disch = true 
 +    end 
 +  end 
 +  if disch then 
 +    if spDischType == "linear" then 
 +      local dischDiff = dischMax - dischSp 
 +      if dischDiff == 0 then dischDiff = 1 end 
 +      if dischVal > dischSp then 
 +        local dischErr = dischVal - dischSp 
 +        dischPct = dischErr / dischDiff 
 +        if dischPct > 1 then dischPct = 1 end 
 +        if dischPct < 0 then dischPct = 0 end 
 +        dischOutput = (1 - dischPct) * 100 
 +      else 
 +        dischOutput = 100 
 +      end 
 +    else 
 +      set_gbl("PIDdischEnable",1) 
 +      set_gbl("PIDdischPFactor",dischPIDPFactor) 
 +      set_gbl("PIDdischIFactor",dischPIDIFactor) 
 +      set_gbl("PIDdischDFactor",dischPIDDFactor) 
 +      set_gbl("PIDdischSp",dischSp) 
 +      set_gbl("PIDdischRevAct",1) 
 +      set_gbl("PIDdischDeadband",0.2) 
 +      local dischPidOutput = doPid("disch",dischVal) 
 +      dischOutput = dischPidOutput 
 +    end 
 +  else 
 +    dischOutput = 100 
 +  end 
 + 
 +   
 +  local minOutput = 100 
 +  local winning = 0 
 +  if suctOutput < minOutput then 
 +    minOutput = suctOutput 
 +    winning = 1 
 +  end 
 +  if dischOutput < minOutput then 
 +    minOutput = dischOutput 
 +    winning = 2 
 +  end 
 + 
 +  if suctOutput == dischOutput then 
 +    winning = 0 
 +  end 
 + 
 +  if winning == 0 then 
 +    set_gbl("PIDsuctMax",100) 
 +    set_gbl("PIDdischMax",100) 
 +  end 
 + 
 +  if winning == 1 then 
 +    set_gbl("PIDdischMax",math.min(suctOutput + 2,100)) 
 +    set_gbl("integraldisch",0) 
 +    set_gbl("lastErrdisch",0) 
 +    set_gbl("outputSumdisch",0) 
 +    set_gbl("PIDsuctMax",100) 
 +  end 
 +  if winning == 2 then 
 +    set_gbl("PIDsuctMax",math.min(dischOutput + 2,100)) 
 +    set_gbl("integralsuct",0) 
 +    set_gbl("lastErrsuct",0) 
 +    set_gbl("outputSumsuct",0) 
 +    set_gbl("PIDdischMax",100) 
 +  end 
 + 
 +  local recycleMinOutput = minOutput 
 + 
 +  local manOutput = 0 
 +  --******************************************************************** 
 +  local manMode = 0 
 +  local manTerm = tonumber_def(get_gbl("manTerm",0),0) 
 +  local manChan = tonumber_def(get_gbl("manChan",0),0) 
 +  if manTerm > 0 and manChan > 0 then 
 +    local manInput = get_channel_val(manTerm,manChan) 
 +    if manInput > 0.5 then 
 +      manMode = 0 
 +      set_sVirt("SpeedControl","Auto"
 +    else 
 +      manMode = 1 
 +      set_sVirt("SpeedControl","Manual"
 +    end 
 +  else 
 +    if get_sVirt("SpeedControl","Auto") == "Auto" then 
 +      manMode = 0 
 +    else 
 +      manMode = 1 
 +    end 
 +  end 
 + 
 +  --if manMode == 1 and get_state() == 8 then 
 +  local manSpeed = get_sVirt("ManualSpeed",0) 
 +  local idleSpeed = get_gbl("idleSpeed",0) 
 +  local lowSpeed = get_gbl("lowSpeed",0) 
 +  local highSpeed = get_gbl("highSpeed",0) 
 +  local maxSpeed = get_gbl("maxSpeed",0) 
 +  local diff = highSpeed - lowSpeed 
 +  if diff < 0 then diff = 0 end 
 +  local maxDiff = maxSpeed - idleSpeed 
 +  if maxDiff < 0 then maxDiff = 0 end 
 + 
 +  if get_sVirt("speedBump",0) ~= 0 then 
 +    local si = get_gbl("SpeedIncrement",0) 
 +    local sip = get_param("SpeedIncrement",0) 
 +    if sip ~= 0 then si = sip end 
 +    manSpeed = manSpeed + (si * get_sVirt("speedBump",0)) 
 +    set_sVirt("speedBump",0) 
 +  end 
 + 
 +  if get_sVirt("AutoManBump",0) > 0 then 
 +    set_sVirt("SpeedControl","Auto"
 +    set_sVirt("AutoManBump",0) 
 +  end 
 + 
 +  if get_sVirt("AutoManBump",0) < 0 then 
 +    set_sVirt("SpeedControl","Manual"
 +    set_sVirt("AutoManBump",0) 
 +  end 
 + 
 +  if manMode == 1 then 
 +    local manSpeedTerm = tonumber_def(get_gbl("manSpeedTerm",0),0) 
 +    local manSpeedChan = tonumber_def(get_gbl("manSpeedChan",0),0) 
 +    if manSpeedTerm > 0 and manSpeedChan > 0 then --*** USE SPEED POT TO SET SPEED 
 +      local speedInput = tonumber(get_channel_val(manSpeedTerm,manSpeedChan)) 
 +      local speedPct = (speedInput / 5) * 100 
 +      if speedPct > 100 then speedPct = 100 end 
 +      if speedPct < 0 then speedPct = 0 end 
 +      manOutput = speedPct 
 +      manSpeed = math.floor((speedPct / 100) * diff + lowSpeed + 0.5) 
 +    else -- Use ManualSpeed to set speed 
 +      manOutput = ((manSpeed - lowSpeed) / diff) * 100.0 
 +      if manOutput < 0 then manOutput = 0 end 
 +      if manOutput > 100 then manOutput = 100 end 
 +    end 
 +    minOutput = manOutput 
 +  else 
 +    --speedTarget = 
 +    local stRpm = (speedTarget/100) * maxDiff + idleSpeed 
 +    if stRpm < lowSpeed then stRpm = lowSpeed end 
 +    if stRpm > highSpeed then stRpm = highSpeed end 
 +    manSpeed = math.floor(stRpm) 
 +  end 
 + 
 +  if manSpeed < lowSpeed then 
 +    manSpeed = lowSpeed 
 +  end 
 +  if manSpeed > highSpeed then 
 +    manSpeed = highSpeed 
 +  end 
 + 
 +  set_sVirt("ManualSpeed",manSpeed) 
 + 
 + 
 + 
 +  --******************************************************************** 
 + 
 + 
 +  local output1 = 0 
 +  local output2 = 0 
 +  if spSuctType == "pid" or spDischType == "pid" then 
 +    output1 = map_range(outputLow,outputHigh,minOutput) 
 +    set_sVirt("out1",output1) 
 +    output2 = map_range(outputLow2,outputHigh2,recycleMinOutput) 
 +    set_sVirt("out2",output2) 
 +    local hasRPM = idleSpeed > 0 and lowSpeed > 0 and highSpeed > 0 and maxSpeed > 0 
 +    if outputTerm and outputChan then 
 +      if hasRPM then 
 +        local speedRpm = output1 / 100  * (highSpeed - lowSpeed) + lowSpeed 
 +        speedTarget = (speedRpm - idleSpeed) / (maxSpeed - idleSpeed) * 100 
 +      else 
 +        speedTarget = output1 
 +      end 
 +    end 
 +    if recycleTerm and recycleChan then 
 +      set_ao_val(recycleTerm,recycleChan,output2) 
 +    end 
 + 
 +    if get_state() == 9 then 
 +      speedTarget = get_sGbl("speedTarget",0) 
 +      if speedTarget > 0 then speedTarget = speedTarget - rampRate1 end 
 +      if speedTarget < 0 then speedTarget = 0 end 
 +    end 
 +    if get_state() < 8 then speedTarget = 0 end 
 +    set_sGbl("speedTarget",speedTarget) 
 +    set_ao_val(outputTerm,outputChan,speedTarget) 
 +    set_sVirt("spTarget",speedTarget) 
 + 
 +    if hasRPM then 
 +      local sRpm = (speedTarget/100) * maxDiff + idleSpeed 
 +      set_sVirt("Speed Target",math.floor(sRpm + 0.5)) 
 +    end 
 + 
 + 
 + 
 +  else 
 + 
 +    -- Remember that minOutput is 0 - 100 pct of lowSpeed <-> highSpeed 
 +    -- We need to convert this to 0 - 100 pct of idleSpeed <-> maxSpeed 
 +    local suctPct = map_range(outputLow,outputHigh,minOutput) 
 +    local speedRpm = suctPct / 100  * (highSpeed - lowSpeed) + lowSpeed 
 +    minOutput = (speedRpm - idleSpeed) / (maxSpeed - idleSpeed) * 100 
 + 
 + 
 + 
 +    if minOutput <= speedTarget then 
 +      speedTarget = speedTarget - rampRate1 
 +      if speedTarget < minOutput then speedTarget = minOutput end 
 +    else 
 +        speedTarget = speedTarget + rampRate1 
 +        if speedTarget > minOutput then speedTarget = minOutput end 
 +        if speedTarget > maxLoad then speedTarget = maxLoad end 
 +    end 
 +    if speedTarget > maxLoad then speedTarget = maxLoad end 
 +    if speedTarget < minLoad then speedTarget = minLoad end 
 + 
 +    if recycleCtrl then 
 +      local recyclePct = map_range(outputLow2,outputHigh2,recycleMinOutput) 
 +      if recyclePct <= recycleTarget then 
 +        recycleTarget = recycleTarget - rampRate2 
 +        if recycleTarget < recyclePct then recycleTarget = recyclePct end 
 +      else 
 +        recycleTarget = recycleTarget + rampRate2 
 +        if recycleTarget > recyclePct then recycleTarget = recyclePct end 
 +      end 
 +      if recycleTarget > maxRecycle then recycleTarget = maxRecycle end 
 +      if recycleTarget < minRecycle then recycleTarget = minRecycle end 
 +      local recycleOutput = recycleTarget 
 +      if get_state() < 8 then 
 +        recycleTarget = 0 
 +      end 
 +      if recycleRevAct == 1 then 
 +        recycleOutput = 100 - recycleOutput 
 +      end 
 +      set_ao_val(recycleTerm,recycleChan,recycleOutput) 
 +      set_sGbl("recycleTarget",recycleTarget) 
 +      set_sVirt("recycleTarget",recycleTarget) 
 +    end 
 + 
 +    if get_state() == 9 then 
 +      speedTarget = get_sGbl("speedTarget",0) 
 +      if speedTarget > 0 then speedTarget = speedTarget - rampRate1 end 
 +      if speedTarget < 0 then speedTarget = 0 end 
 +    end 
 +    if get_state() < 8 then speedTarget = 0 end 
 +    set_sGbl("speedTarget",speedTarget) 
 +    set_ao_val(outputTerm,outputChan,speedTarget) 
 +    set_sVirt("spTarget",speedTarget) 
 +    local sRpm = (speedTarget/100) * maxDiff + idleSpeed 
 +    set_sVirt("Speed Target",math.floor(sRpm + 0.5)) 
 + 
 + 
 +  end 
 + 
 +</file>