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/01/25 16:22] (current)
twebster
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 
 + 
 +==== - DE4000 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>