From 4bb115fc0271461662e91654b4b6eeec8888d31c Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Mon, 16 Apr 2018 15:45:20 -0700 Subject: [PATCH 01/27] Added PID tuner for jet engine (hopefully someday the PID tuner can be abstracted to work with many different systems/control problems. --- package-lock.json | 43 ++ package.json | 2 + src/components/App/App.vue | 2 + .../Calculators/Software/Pid/Calc.js | 10 + .../Software/Pid/JetEngineModel.js | 36 ++ .../Calculators/Software/Pid/MainView.vue | 459 ++++++++++++++++++ .../Calculators/Software/Pid/Pid.js | 147 ++++++ .../Calculators/Software/Pid/diagram.png | Bin 0 -> 8608 bytes .../Calculators/Software/Pid/diagram.vsd | Bin 0 -> 75264 bytes .../Calculators/Software/Pid/grid-icon.png | Bin 0 -> 3320 bytes 10 files changed, 699 insertions(+) create mode 100644 src/components/Calculators/Software/Pid/Calc.js create mode 100644 src/components/Calculators/Software/Pid/JetEngineModel.js create mode 100644 src/components/Calculators/Software/Pid/MainView.vue create mode 100644 src/components/Calculators/Software/Pid/Pid.js create mode 100644 src/components/Calculators/Software/Pid/diagram.png create mode 100644 src/components/Calculators/Software/Pid/diagram.vsd create mode 100644 src/components/Calculators/Software/Pid/grid-icon.png diff --git a/package-lock.json b/package-lock.json index 7069ee1b..915ce888 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1423,6 +1423,39 @@ } } }, + "chart.js": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz", + "integrity": "sha512-90wl3V9xRZ8tnMvMlpcW+0Yg13BelsGS9P9t0ClaDxv/hdypHDr/YAGf+728m11P5ljwyB0ZHfPKCapZFqSqYA==", + "requires": { + "chartjs-color": "2.2.0", + "moment": "2.22.1" + } + }, + "chartjs-color": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz", + "integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=", + "requires": { + "chartjs-color-string": "0.5.0", + "color-convert": "0.5.3" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, + "chartjs-color-string": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz", + "integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==", + "requires": { + "color-name": "1.1.3" + } + }, "check-types": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.3.0.tgz", @@ -6345,6 +6378,11 @@ } } }, + "moment": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", + "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -9246,6 +9284,11 @@ "resolved": "https://registry.npmjs.org/vue-select/-/vue-select-2.4.0.tgz", "integrity": "sha512-WxQc7t65ht3YSwSgcSdHFU8cSOWKpvH6n1B/Z9ua44hMB2oVcy0Mieu4qjMPrYx3AQQ8Y8F+pfNIylRZ0t3IVA==" }, + "vue-slider-component": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-2.6.2.tgz", + "integrity": "sha512-hg3nlBpRMxLZU8BUPeTCY1qk97LD04q8UFVmBBUhMlw9jS2kzMDEsgP+CFApmL4Fv4hXjL8GNKS0scwe1GDoSg==" + }, "vue-strap": { "version": "1.1.40", "resolved": "https://registry.npmjs.org/vue-strap/-/vue-strap-1.1.40.tgz", diff --git a/package.json b/package.json index 418562ae..0a9aab65 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "bootstrap-vue": "^0.8.2", "chai": "^3.5.0", "chalk": "^1.1.3", + "chart.js": "^2.7.2", "chromedriver": "^2.27.2", "connect-history-api-fallback": "^1.3.0", "copy-webpack-plugin": "^4.0.1", @@ -88,6 +89,7 @@ "vue-material": "^0.7.1", "vue-router": "^2.3.0", "vue-select": "^2.4.0", + "vue-slider-component": "^2.6.2", "vue-strap": "^1.1.36", "vue-style-loader": "^2.0.0", "vue-template-compiler": "^2.1.10", diff --git a/src/components/App/App.vue b/src/components/App/App.vue index a9d933f8..bfdefde1 100644 --- a/src/components/App/App.vue +++ b/src/components/App/App.vue @@ -63,6 +63,7 @@ import { mapPlotter } from "../Calculators/Geospatial/MapPlotter/Calc"; import { crcCalculator } from '../Calculators/Software/Crc/Calc' + import { pidTuner } from '../Calculators/Software/Pid/Calc' export default { name: 'app', @@ -209,6 +210,7 @@ // ============================================ // this.$store.dispatch('registerCalc', crcCalculator) + this.$store.dispatch('registerCalc', pidTuner) // Show the overlay by default. // THIS MUST BE DONE BEFORE handlerRouteChange() IS CALLED diff --git a/src/components/Calculators/Software/Pid/Calc.js b/src/components/Calculators/Software/Pid/Calc.js new file mode 100644 index 00000000..20ff0ded --- /dev/null +++ b/src/components/Calculators/Software/Pid/Calc.js @@ -0,0 +1,10 @@ +import MainView from './MainView' + +export var pidTuner = { + displayName: 'PID Control', + description: 'PID control.', + imagePath: require('./grid-icon.png'), + category: [ 'Software' ], + tags: [ 'pid', 'control', 'system', 'process', 'plant' ], + mainView: MainView +} diff --git a/src/components/Calculators/Software/Pid/JetEngineModel.js b/src/components/Calculators/Software/Pid/JetEngineModel.js new file mode 100644 index 00000000..55494ea8 --- /dev/null +++ b/src/components/Calculators/Software/Pid/JetEngineModel.js @@ -0,0 +1,36 @@ +/* eslint-disable */ +export class JetEngineModel { + + constructor(fuelConstant, dragConstant, maxAccel_radPss) { + this.fuelConstant = fuelConstant + this.dragConstant = dragConstant + this.maxAccel_radPss = maxAccel_radPss + this.rotVel_radPs = 0.0 + + this.lastUpdateTime_s = 0.0 + } + + update(fuelFlow_lPmin, timeStep_s) { + console.log('JetEngineModel.update() called with fuelFlow_lPmin = ' + fuelFlow_lPmin + ', timeStep_s = ' + timeStep_s + '.') + + // Ffuel - Fdrag = ma + let rotAccel_radPss = this.fuelConstant*fuelFlow_lPmin + this.dragConstant*Math.pow(this.rotVel_radPs, 1) + console.log('rotAccel_radPss = ' + rotAccel_radPss) + + if(rotAccel_radPss > this.maxAccel_radPss) + rotAccel_radPss = this.maxAccel_radPss + else if(rotAccel_radPss < -this.maxAccel_radPss) + rotAccel_radPss = -this.maxAccel_radPss + + let changeInRotVel_radPs = rotAccel_radPss * timeStep_s + // console.log('changeInRotVel = ' + changeInRotVel) + + this.rotVel_radPs = this.rotVel_radPs + changeInRotVel_radPs + } + + getRotVel_radPs() { + return this.rotVel_radPs + } + +} +/* eslint-enable */ diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue new file mode 100644 index 00000000..51ea69fd --- /dev/null +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -0,0 +1,459 @@ + + + + + + diff --git a/src/components/Calculators/Software/Pid/Pid.js b/src/components/Calculators/Software/Pid/Pid.js new file mode 100644 index 00000000..de8df2b2 --- /dev/null +++ b/src/components/Calculators/Software/Pid/Pid.js @@ -0,0 +1,147 @@ +/* eslint-disable */ + +"use strict"; + +export const IntegralLimitModes = { + NONE: 'None', + CONSTANT_LIMITED: 'Constant Limited', + OUTPUT_LIMITED: 'Output Limited' +}; + +export class Pid { + constructor(pConstant, iConstant, dConstant) { + this.pConstant = pConstant + this.iConstant = iConstant + this.dConstant = dConstant + + this.setPoint = 0.0 + this.iValue = 0.0 + this.previousError = 0.0 + + // Integral limiting (default to OFF) + this.integralLimitSettings = {} + this.integralLimitSettings.mode = IntegralLimitModes.NONE + + // Output limiting (default to OFF) + this.enableOutputLimiting = false + this.outputLimMin = 0.0 + this.outputLimMax = 0.0 + } + + setSetPoint(value) { + console.log('setSetPoint() called. value = ' + value) + this.setPoint = value + } + + setConstants(pConstant, iConstant, dConstant) { + console.log('setConstants() called. pConstant = ' + pConstant + ', iConstant = ' + iConstant + ', dConstant = ' + dConstant) + this.pConstant = pConstant + this.iConstant = iConstant + this.dConstant = dConstant + } + + setOutputLimits(min, max) { + this.outputLimMin = min + this.outputLimMax = max + this.enableOutputLimiting = true + } + + setIntegralLimit(options) { + console.log('setIntegralLimit() called with settings = ') + console.log(options) + if(options.mode === IntegralLimitModes.CONSTANT_LIMITED) { + if(!options.hasOwnProperty('min') || !options.hasOwnProperty('max')) + throw new Error('Provided options must have min and max property when mode === CONSTANT_LIMITED.') + + if(typeof options.min !== 'number' || typeof options.max !== 'number') + throw new Error('Provided options.min and options.max must both be numbers.') + } else if(options.mode === IntegralLimitModes.OUTPUT_LIMITED) { + // Output limiting has to be enabled for this mode to work + if(!this.enableOutputLimiting) + throw new Error('Integral limiting set to OUTPUT_LIMITED but output limiting is not enabled.') + } + + this.integralLimitSettings = options + } + + run(currentValue, deltaTime_s) { + + console.log('Pid.run() called. currentValue = ' + currentValue + ', deltaTime_s = ' + deltaTime_s) + + // Error positive if we need to "go forward" + let error = this.setPoint - currentValue + console.log('error = ' + error) + + // Porportional control + let pValue = error * this.pConstant + console.log('pValue = ' + pValue) + + // Integral control + this.iValue += error*deltaTime_s*this.iConstant + console.log('iValue (before limiting) = ' + this.iValue) + + // Limit integral control + console.log('this.integralLimitSettings =') + console.log(this.integralLimitSettings) + if(this.integralLimitSettings.mode === IntegralLimitModes.CONSTANT_LIMITED) { + console.log('Limiting integral term with constant.') + if(this.iValue > this.integralLimitSettings.max) + this.iValue = this.integralLimitSettings.max + else if(this.iValue < this.integralLimitSettings.min) + this.iValue = this.integralLimitSettings.min + } + + console.log('iValue (after limiting) = ' + this.iValue) + + // Derivative control + let deltaError = error - this.previousError + + let dValue = deltaError*this.dConstant + console.log('dValue = ' + dValue) + + let output = pValue + this.iValue + dValue + console.log('output = ' + output) + + this.previousError = error + + // Limit output if output limiting is enabled + if(this.enableOutputLimiting) { + if(output > this.outputLimMax) { + console.log('Desired output is above max. limit.') + if(this.integralLimitSettings.mode === IntegralLimitModes.OUTPUT_LIMITED) { + this.limitIntegralTerm(output) + } + output = this.outputLimMax + } else if(output < this.outputLimMin) { + console.log('Desired output (' + output + ') is below min. limit (' + this.outputLimMin + '.') + if(this.integralLimitSettings.mode === IntegralLimitModes.OUTPUT_LIMITED) { + this.limitIntegralTerm(output) + } + output = this.outputLimMin + } + } + + return output + + } + + limitIntegralTerm(output) { + console.log('limitIntegralTerm() called. output = ' + output) + console.log('iValue (before limiting) = ' + this.iValue) + if(output > this.outputLimMax) { + let difference = output - this.outputLimMax + console.log('output - outputLimMax = ' + difference) + if(this.iValue > 0.0) + // Reduce I value, but make sure it doesn't go negative! + this.iValue = Math.max(this.iValue - difference, 0) + } else if(output < this.outputLimMin) { + let difference = output - this.outputLimMin + console.log('output - outputLimMin = ' + difference) + if(this.iValue < 0.0) + // Increase I value, but make sure it doesn't go positive! + this.iValue = Math.min(this.iValue - difference, 0) + } + console.log('iValue (after limiting) = ' + this.iValue) + } +} +/* eslint-enable */ diff --git a/src/components/Calculators/Software/Pid/diagram.png b/src/components/Calculators/Software/Pid/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..20885b4065019c242fafe9ec4e28835d03b04043 GIT binary patch literal 8608 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

Tsmc5$crk48onB<3>9{TEGx<}PWZB#QQXqz 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 literal 0 HcmV?d00001 diff --git a/src/components/Calculators/Software/Pid/grid-icon.png b/src/components/Calculators/Software/Pid/grid-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..97568490d608d14fef9c2acbdf3c847e00c8b1f1 GIT binary patch literal 3320 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 O0Eo-hrVYku-2VX&buPRB literal 0 HcmV?d00001 From 277a1b7c9426d5f557571659a0f92e9386960a51 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Mon, 16 Apr 2018 17:19:44 -0700 Subject: [PATCH 02/27] Added BoostrapVue library (may remove this in future as it's CSS files clashes with our use of fieldsets). --- package-lock.json | 232 ++++++++++++++++-- package.json | 2 +- .../Calculators/Software/Pid/MainView.vue | 44 ++-- src/main.js | 8 +- 4 files changed, 244 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 915ce888..c960ac2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -895,6 +895,23 @@ "babel-types": "6.26.0" } }, + "babel-polyfill": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz", + "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=", + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.3", + "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + } + } + }, "babel-preset-es2015": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", @@ -1134,25 +1151,21 @@ } }, "bootstrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.0.0.tgz", - "integrity": "sha512-gulJE5dGFo6Q61V/whS6VM4WIyrlydXfCgkE+Gxe5hjrJ8rXLLZlALq7zq2RPhOc45PSwQpJkrTnc2KgD6cvmA==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.0.tgz", + "integrity": "sha512-kCo82nE8qYVfOa/Z3hL98CPgPIEkh6iPdiJrUJMQ9n9r0+6PEET7cmhLlV0XVYmEj5QtKIOaSGMLxy5jSFhKog==" }, "bootstrap-vue": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-0.8.3.tgz", - "integrity": "sha1-KPa33yR+FWnvsM8luUANlzhIEbo=", + "version": "2.0.0-rc.7", + "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.7.tgz", + "integrity": "sha512-WwP06iVNRfKlerLBxAhK2mFduGCZBnLBgp+IED4iN9R1K/W7APxnT8Sbih4TBgsbKyZ57JqWdHgrLt1lgl/TYg==", "requires": { - "bootstrap": "4.0.0", - "tether": "1.4.3", - "vue": "2.1.10" - }, - "dependencies": { - "vue": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.1.10.tgz", - "integrity": "sha1-ySNcpIx5JRN75YB4MqxOOsGAQns=" - } + "bootstrap": "4.1.0", + "lodash.get": "4.4.2", + "lodash.startcase": "4.4.0", + "opencollective": "1.0.3", + "popper.js": "1.14.3", + "vue-functional-data-merge": "2.0.6" } }, "brace-expansion": { @@ -1423,6 +1436,11 @@ } } }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, "chart.js": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz", @@ -2789,6 +2807,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "0.4.19" + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -3358,6 +3384,26 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "1.0.2" + } + } + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -5236,6 +5282,11 @@ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -5876,6 +5927,11 @@ } } }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -5921,6 +5977,11 @@ "resolved": "https://registry.npmjs.org/lodash.rest/-/lodash.rest-4.0.5.tgz", "integrity": "sha1-lU73UEkmIDjJbR/Jiyj9r58Hcqo=" }, + "lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha1-lDbjTtJgk+1/+uGTYUQ1CRXZrdg=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -6475,6 +6536,15 @@ "lower-case": "1.1.4" } }, + "node-fetch": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", + "integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=", + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, "node-libs-browser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", @@ -6616,6 +6686,121 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" }, + "opencollective": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/opencollective/-/opencollective-1.0.3.tgz", + "integrity": "sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE=", + "requires": { + "babel-polyfill": "6.23.0", + "chalk": "1.1.3", + "inquirer": "3.0.6", + "minimist": "1.2.0", + "node-fetch": "1.6.3", + "opn": "4.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "inquirer": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz", + "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=", + "requires": { + "ansi-escapes": "1.4.0", + "chalk": "1.1.3", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.5", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx": "4.1.0", + "string-width": "2.1.1", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "1.2.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "2.1.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "3.0.0" + } + } + } + } + } + }, "opener": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", @@ -7033,6 +7218,11 @@ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=" }, + "popper.js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz", + "integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=" + }, "postcss": { "version": "5.2.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", @@ -8204,6 +8394,11 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" + }, "rx-lite": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", @@ -9233,6 +9428,11 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.13.tgz", "integrity": "sha512-3D+lY7HTkKbtswDM4BBHgqyq+qo8IAEE8lz8va1dz3LLmttjgo0FxairO4r1iN2OBqk8o1FyL4hvzzTFEdQSEw==" }, + "vue-functional-data-merge": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-2.0.6.tgz", + "integrity": "sha512-eivElFOJwhXJopKlq71/8onDxOKK4quPwWGFF9yIVjpU2sNzxISRpufu18bh674ivSADuEAPU2OhT+vrH0E9Mg==" + }, "vue-hot-reload-api": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.2.4.tgz", diff --git a/package.json b/package.json index 0a9aab65..bd41eeeb 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "babel-preset-stage-2": "^6.22.0", "babel-register": "^6.22.0", "big-integer": "1.6.17", - "bootstrap-vue": "^0.8.2", + "bootstrap-vue": "2.0.0-rc.7", "chai": "^3.5.0", "chalk": "^1.1.3", "chart.js": "^2.7.2", diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue index 51ea69fd..8d7f4f46 100644 --- a/src/components/Calculators/Software/Pid/MainView.vue +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -1,5 +1,5 @@ @@ -134,18 +139,9 @@ export default { // Enumeration of run modes. Used to populate select input. runModes: [ - { - text: "Control Fuel Rate (no PID)", - value: "RUN_MODE_CONTROL_FUEL_RATE" - }, - { - text: "Manual RPM Control (PID)", - value: "RUN_MODE_PID_MANUAL_RPM_CONTROL" - }, - { - text: "Auto RPM Step Changes (PID)", - value: "RUN_MODE_PID_AUTO_RPM_STEP_CHANGES" - } + { text: "Control Fuel Rate (no PID)", value: "RUN_MODE_CONTROL_FUEL_RATE" }, + { text: "Manual RPM Control (PID)", value: "RUN_MODE_PID_MANUAL_RPM_CONTROL"}, + { text: "Auto RPM Step Changes (PID)", value: "RUN_MODE_PID_AUTO_RPM_STEP_CHANGES"} ], selectedRunMode: "RUN_MODE_PID_MANUAL_RPM_CONTROL", // Selected run mode (selected by user) diff --git a/src/main.js b/src/main.js index 12e833e5..f2885340 100644 --- a/src/main.js +++ b/src/main.js @@ -11,9 +11,15 @@ import VueMaterial from 'vue-material' import 'vue-material/dist/vue-material.css' Vue.use(VueMaterial) +// BootstrapVue used for Buttons +import BootstrapVue from 'bootstrap-vue' +Vue.use(BootstrapVue) +// import 'bootstrap/dist/css/bootstrap.css' +// import 'bootstrap-vue/dist/bootstrap-vue.css' + // Setup v-select component import vSelect from 'vue-select' -Vue.component(vSelect) +Vue.component('v-select', vSelect) // KeenUI is used for the collapsable "Info" // sections on each calculator From 52e315be3da0b9d7da34de1c11d3fb1fd68af733 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Tue, 17 Apr 2018 18:15:18 -0700 Subject: [PATCH 03/27] Created custom Vue button and panel components. --- .../Calculators/Software/Pid/MainView.vue | 380 ++++++++++-------- src/main.js | 12 + src/misc/MnButton/MnButton.vue | 73 ++++ src/misc/Panel/Panel.vue | 54 +++ 4 files changed, 359 insertions(+), 160 deletions(-) create mode 100644 src/misc/MnButton/MnButton.vue create mode 100644 src/misc/Panel/Panel.vue diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue index 8d7f4f46..e3b7d899 100644 --- a/src/components/Calculators/Software/Pid/MainView.vue +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -1,113 +1,134 @@ + + + diff --git a/src/misc/Panel/Panel.vue b/src/misc/Panel/Panel.vue new file mode 100644 index 00000000..0e936169 --- /dev/null +++ b/src/misc/Panel/Panel.vue @@ -0,0 +1,54 @@ + + + + + + From d805e71ca7a94271c52c7540a75fc431be79d89d Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Wed, 18 Apr 2018 10:31:42 -0700 Subject: [PATCH 04/27] Added chart which shows the P, I, D terms and the sum (output) of the PID controller. --- .editorconfig | 2 +- .../Calculators/Software/Pid/MainView.vue | 432 ++++++++++++------ .../Calculators/Software/Pid/Pid.js | 14 + 3 files changed, 297 insertions(+), 151 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9d08a1a8..e291365a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] charset = utf-8 indent_style = space -indent_size = 2 +indent_size = 4 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue index e3b7d899..ae671c3b 100644 --- a/src/components/Calculators/Software/Pid/MainView.vue +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -2,10 +2,16 @@

Jet Engine PID Control

+
+ +
+ +
+
@@ -33,6 +39,8 @@ style="width: 100px; height: 50px;">
{{ !simulationRunning ? 'START' : 'STOP' }}
+ +
@@ -132,7 +140,6 @@ diff --git a/src/components/Calculators/Software/Pid/Pid.js b/src/components/Calculators/Software/Pid/Pid.js index de8df2b2..e5b17fa6 100644 --- a/src/components/Calculators/Software/Pid/Pid.js +++ b/src/components/Calculators/Software/Pid/Pid.js @@ -26,6 +26,8 @@ export class Pid { this.enableOutputLimiting = false this.outputLimMin = 0.0 this.outputLimMax = 0.0 + + this.lastOutput = 0.0 } setSetPoint(value) { @@ -121,10 +123,22 @@ export class Pid { } } + // Save the calculated P, I, D and output values, as the user may want to + // inspect these by calling getLastTerms() + this.lastTerms = { + p: pValue, + i: this.iValue, + d: dValue, + output: output + } return output } + getLastTerms () { + return this.lastTerms + } + limitIntegralTerm(output) { console.log('limitIntegralTerm() called. output = ' + output) console.log('iValue (before limiting) = ' + this.iValue) From 4549de57632680269295541e8122a4e34260251b Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Wed, 18 Apr 2018 11:02:47 -0700 Subject: [PATCH 05/27] Fixed a layout issue with the PID settings panel. --- .../Calculators/Software/Pid/MainView.vue | 90 ++++++++++--------- src/misc/Panel/Panel.vue | 2 +- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue index ae671c3b..05406be2 100644 --- a/src/components/Calculators/Software/Pid/MainView.vue +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -3,16 +3,18 @@

Jet Engine PID Control

-
- +
+
-
- +
+
-
+ +
@@ -195,25 +197,29 @@ export default { ] }, options: { - scales: { - xAxes: [ - { - type: "linear", - scaleLabel: { - display: true, - labelString: "Time (s)" + title: { + display: true, + text: 'Plant Output And PID Set-Point' + }, + scales: { + xAxes: [ + { + type: "linear", + scaleLabel: { + display: true, + labelString: "Time (s)" + } } - } - ], - yAxes: [ - { - scaleLabel: { - display: true, - labelString: "Rotational Velocity (rpm)" + ], + yAxes: [ + { + scaleLabel: { + display: true, + labelString: "Rotational Velocity (rpm)" + } } - } - ] - } + ] + } } }, chart: null, @@ -244,7 +250,7 @@ export default { fill: false }, { - label: "Output", + label: "Output (P + I + D)", backgroundColor: "rgba(255, 60, 92, 0.5)", borderColor: "rgba(255, 60, 92, 0.5)", data: [], @@ -253,25 +259,29 @@ export default { ] }, options: { - scales: { - xAxes: [ - { - type: "linear", - scaleLabel: { - display: true, - labelString: "Time (s)" + title: { + display: true, + text: 'PID Controller Output' + }, + scales: { + xAxes: [ + { + type: "linear", + scaleLabel: { + display: true, + labelString: "Time (s)" + } } - } - ], - yAxes: [ - { - scaleLabel: { - display: true, - labelString: "PID Effort" + ], + yAxes: [ + { + scaleLabel: { + display: true, + labelString: "PID Effort" + } } - } - ] - } + ] + } } }, pidTermsChart: null, diff --git a/src/misc/Panel/Panel.vue b/src/misc/Panel/Panel.vue index 0e936169..0182db02 100644 --- a/src/misc/Panel/Panel.vue +++ b/src/misc/Panel/Panel.vue @@ -44,7 +44,7 @@ div.panel-content { width: 100%; - height: 100%; + /* height: 100%; */ padding: 10px; } From da143a187753ec4d56f3c973b6244f6356072dda Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Wed, 18 Apr 2018 11:25:50 -0700 Subject: [PATCH 06/27] Fixed some more layout issues with the PID tuner tool. --- .../Calculators/Software/Pid/MainView.vue | 120 +++++++++--------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue index 05406be2..9b45a99d 100644 --- a/src/components/Calculators/Software/Pid/MainView.vue +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -1,34 +1,34 @@ From b2851fa8c0aa588d1d16edb9fb98bb2c5b5a4b1f Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Wed, 18 Apr 2018 16:09:58 -0700 Subject: [PATCH 10/27] Moved process model for jet engine to text file. Added raw-loader webpack module. --- build/webpack.base.conf.js | 4 + package-lock.json | 6 + package.json | 4 +- .../Software/Pid/JetEngineModel.txt | 35 +++ .../Calculators/Software/Pid/MainView.vue | 216 ++++++++++++------ .../Software/Pid/{ => old}/JetEngineModel.js | 0 6 files changed, 193 insertions(+), 72 deletions(-) create mode 100644 src/components/Calculators/Software/Pid/JetEngineModel.txt rename src/components/Calculators/Software/Pid/{ => old}/JetEngineModel.js (100%) diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index e0aea12e..999605d8 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -33,6 +33,10 @@ module.exports = { }, module: { rules: [ + { + test: /\.txt$/, + use: 'raw-loader' + }, { test: /\.(js|vue)$/, loader: 'eslint-loader', diff --git a/package-lock.json b/package-lock.json index c960ac2a..edd5852d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7981,6 +7981,12 @@ "unpipe": "1.0.0" } }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", diff --git a/package.json b/package.json index bd41eeeb..854f8e79 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,9 @@ "webpack-hot-middleware": "^2.16.1", "webpack-merge": "^2.6.1" }, - "devDependencies": {}, + "devDependencies": { + "raw-loader": "^0.5.1" + }, "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" diff --git a/src/components/Calculators/Software/Pid/JetEngineModel.txt b/src/components/Calculators/Software/Pid/JetEngineModel.txt new file mode 100644 index 00000000..6b3ef539 --- /dev/null +++ b/src/components/Calculators/Software/Pid/JetEngineModel.txt @@ -0,0 +1,35 @@ +var process = { + init: function() { + console.log('plant.init() called.') + this.fuelConstant = 10000.0 + this.dragConstant = -1.0 + this.maxAccel_radPss = 10000.0 + this.rotVel_radPs = 0.0 + + this.lastUpdateTime_s = 0.0 + }, + update: function (fuelFlow_lPmin, timeStep_s) { + // console.log('JetEngineModel.update() called with fuelFlow_lPmin = ' + fuelFlow_lPmin + ', timeStep_s = ' + timeStep_s + '.') + + // Ffuel - Fdrag = ma + let rotAccel_radPss = this.fuelConstant*fuelFlow_lPmin + this.dragConstant*Math.pow(this.rotVel_radPs, 1) + // console.log('rotAccel_radPss = ' + rotAccel_radPss) + + if(rotAccel_radPss > this.maxAccel_radPss) + rotAccel_radPss = this.maxAccel_radPss + else if(rotAccel_radPss < -this.maxAccel_radPss) + rotAccel_radPss = -this.maxAccel_radPss + + let changeInRotVel_radPs = rotAccel_radPss * timeStep_s + // console.log('changeInRotVel = ' + changeInRotVel) + + this.rotVel_radPs = this.rotVel_radPs + changeInRotVel_radPs + }, + getRotVel_radPs: function () { + return this.rotVel_radPs + } + } + + // Provide an expression to return the object above to + // the eval() function + process diff --git a/src/components/Calculators/Software/Pid/MainView.vue b/src/components/Calculators/Software/Pid/MainView.vue index d7e3fc55..f03fceeb 100644 --- a/src/components/Calculators/Software/Pid/MainView.vue +++ b/src/components/Calculators/Software/Pid/MainView.vue @@ -21,6 +21,15 @@
+ Process: + + +
+
@@ -156,6 +165,8 @@
+ +