Last active
August 1, 2019 07:12
-
-
Save wilberforce/88231ea867bc190199c33538c039201f to your computer and use it in GitHub Desktop.
pid-controller for moddable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2019 Wilberforce | |
* pid-controller - Advanced PID controller based on the Arduino PID library | |
* Based on: Arduino PID Library - Version 1.0.1 by Brett Beauregard <[email protected]> brettbeauregard.com | |
* | |
* Copyright (c) 2016-2017 Moddable Tech, Inc. | |
*/ | |
class PID { | |
constructor(dictionary) { | |
this._input = dictionary.input ? dictionary.input : 0; | |
this._setpoint = dictionary.setpoint ? dictionary.setpoint : 50; | |
this.outputLimits(dictionary.min_output, dictionary.max_output); // default output limits | |
this._sampleTime = dictionary.sampleTime ? dictionary.sampleTime : 100; // default Controller Sample Time is 0.1 seconds | |
this.tunings(dictionary.Kp, dictionary.Ki, dictionary.Kd); | |
this.lastTime = (new Date()).getTime() - this._sampleTime; | |
this.iTerm = 0; | |
this._output = 0; | |
this.mode = dictionary.mode ? dictionary.mode : 0; // Default to PID.MANUAL | |
} | |
/** | |
* compute() | |
* This, as they say, is where the magic happens. This function should be called | |
* when inputs change. The function will decide for itself whether a new | |
* pid Output needs to be computed. returns true when the output is computed, | |
* false when nothing has been done. | |
*/ | |
compute(value) { | |
if (this._mode === PID.MANUAL) { | |
return false; | |
} | |
let now = (new Date()).getTime(); | |
let timeChange = (now - this.lastTime); | |
if (timeChange >= this._sampleTime) { | |
// Compute all the working error variables | |
let input = this._input; | |
let error = this._setpoint - input; | |
this.iTerm += (this.ki * error); | |
let dInput = input - this.lastInput; | |
// Compute PID Output | |
let output = (this.kp * error + this.iTerm - this.kd * dInput) * this._mode; | |
// Clamp to range | |
if (output > this.outMax) { | |
output = this.outMax; | |
} else if (output < this.outMin) { | |
output = this.outMin; | |
} | |
this._output = output; | |
this.lastInput = input; | |
this.lastTime = now; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* tunings(...) | |
* This function allows the controller's dynamic performance to be adjusted. | |
* it's called automatically from the constructor, but tunings can also | |
* be adjusted on the fly during normal operation | |
*/ | |
tunings(Kp, Ki, Kd) { | |
if (Kp < 0 || Ki < 0 || Kd < 0) { | |
new Error("Terms must be postive"); | |
} | |
this.dispKp = Kp; | |
this.dispKi = Ki; | |
this.dispKd = Kd; | |
this._sampleTimeInSec = (this._sampleTime) / 1000; | |
this.kp = Kp; | |
this.ki = Ki * this._sampleTimeInSec; | |
this.kd = Kd / this._sampleTimeInSec; | |
} | |
/** | |
* sampleTime(...) | |
* sets the period, in Milliseconds, at which the calculation is performed | |
*/ | |
set sampleTime(value) { | |
if (value > 0) { | |
let ratio = value / (1.0 * this._sampleTime); | |
this.ki *= ratio; | |
this.kd /= ratio; | |
this._sampleTime = Math.round(value); | |
} | |
} | |
/** | |
* output( ) | |
* Set output level if in manual mode | |
*/ | |
set output(value) { | |
if (this._mode === PID.MANUAL) { | |
new Error("Can't set output value in manual mode"); | |
} | |
if (value > this.outMax) { | |
this._output = value; | |
} else if (value < this.outMin) { | |
value = this.outMin; | |
} | |
this._output = value; | |
} | |
/** | |
* outputLimits(...) | |
* Set the min and max range of the output | |
*/ | |
outputLimits(min, max) { | |
if (min >= max) { | |
return; | |
} | |
this.outMin = min; | |
this.outMax = max; | |
if (this._mode !== PID.MANUAL) { | |
if (this._output > this.outMax) { | |
this._output = this.outMax; | |
} else if (this._output < this.outMin) { | |
this._output = this.outMin; | |
} | |
if (this.iTerm > this.outMax) { | |
this.iTerm = this.outMax; | |
} else if (this.iTerm < this.outMin) { | |
this.iTerm = this.outMin; | |
} | |
} | |
} | |
/** | |
* SetMode(...) | |
* PID.DIRECT - Direct acting process ( +Output leads to +Input, e.g heating ) | |
* PID.REVERSE - Reverse acting process (+Output leads to -Input, e.g. Cooling ) | |
* PID.MANUAL - controller does not adjust output, application does (Override type situation) | |
*/ | |
set mode(m) { | |
this._mode = m; | |
// do all the things that need to happen to ensure a bumpless transfer from manual to automatic mode. | |
this.iTerm = this._output; | |
this.lastInput = this._input; | |
if (this.iTerm > this.outMax) { | |
this.iTerm = this.outMax; | |
} else if (this.iTerm < this.outMin) { | |
this.iTerm = this.outMin; | |
} | |
} | |
get Kp() { | |
return this.dispKp; | |
} | |
get Kd() { | |
return this.dispKd; | |
} | |
get Ki() { | |
return this.dispKi; | |
} | |
get mode() { | |
return this._mode; | |
} | |
get output() { | |
return this._output; | |
} | |
get input() { | |
return this._input; | |
} | |
get setpoint() { | |
return this._setpoint; | |
} | |
set input(current_value) { | |
this._input = current_value; | |
} | |
set setpoint(current_value) { | |
this._setpoint = current_value; | |
} | |
} | |
PID.MANUAL = 0; | |
PID.DIRECT = 1; | |
PID.REVERSE = -1; | |
export { | |
PID as | |
default, PID | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"include": [ | |
"$(MODDABLE)/examples/manifest_base.json", | |
], | |
"modules": { | |
"*": "./main", | |
"PID": "./pid" | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2019 Wilberforce | |
* pid-controller - Advanced PID controller based on the Arduino PID library | |
* Based on: Arduino PID Library - Version 1.0.1 by Brett Beauregard <[email protected]> brettbeauregard.com | |
* | |
* Copyright (c) 2016-2017 Moddable Tech, Inc. | |
*/ | |
class PID { | |
constructor(dictionary) { | |
this._input = dictionary.input ? dictionary.input : 0; | |
this._setpoint = dictionary.setpoint ? dictionary.setpoint : 50; | |
this.outputLimits(dictionary.min_output, dictionary.max_output); // default output limits | |
this._sampleTime = dictionary.setpoint ? dictionary.setpoint : 100; // default Controller Sample Time is 0.1 seconds | |
this.tunings(dictionary.Kp, dictionary.Ki, dictionary.Kd); | |
this.lastTime = (new Date()).getTime() - this._sampleTime; | |
this.iTerm = 0; | |
this._output = 0; | |
this.mode = dictionary.mode ? dictionary.mode : 0; // Default to PID.MANUAL | |
} | |
/** | |
* compute() | |
* This, as they say, is where the magic happens. This function should be called | |
* when inputs change. The function will decide for itself whether a new | |
* pid Output needs to be computed. returns true when the output is computed, | |
* false when nothing has been done. | |
*/ | |
compute() { | |
if (this._mode === PID.MANUAL) { | |
return false; | |
} | |
let now = (new Date()).getTime(); | |
let timeChange = (now - this.lastTime); | |
if (timeChange >= this._sampleTime) { | |
// Compute all the working error variables | |
let input = this._input; | |
let error = this._setpoint - input; | |
this.iTerm += (this.ki * error); | |
let dInput = input - this.lastInput; | |
// Compute PID Output | |
let output = (this.kp * error + this.iTerm - this.kd * dInput) * this._mode; | |
// Clamp to range | |
if (output > this.outMax) { | |
output = this.outMax; | |
} else if (output < this.outMin) { | |
output = this.outMin; | |
} | |
this._output = output; | |
this.lastInput = input; | |
this.lastTime = now; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
/** | |
* tunings(...) | |
* This function allows the controller's dynamic performance to be adjusted. | |
* it's called automatically from the constructor, but tunings can also | |
* be adjusted on the fly during normal operation | |
*/ | |
tunings(Kp, Ki, Kd) { | |
if (Kp < 0 || Ki < 0 || Kd < 0) { | |
new Error("Terms must be postive"); | |
} | |
this.dispKp = Kp; | |
this.dispKi = Ki; | |
this.dispKd = Kd; | |
this._sampleTimeInSec = (this._sampleTime) / 1000; | |
this.kp = Kp; | |
this.ki = Ki * this._sampleTimeInSec; | |
this.kd = Kd / this._sampleTimeInSec; | |
} | |
/** | |
* sampleTime(...) | |
* sets the period, in Milliseconds, at which the calculation is performed | |
*/ | |
set sampleTime(value) { | |
if (value > 0) { | |
let ratio = value / (1.0 * this._sampleTime); | |
this.ki *= ratio; | |
this.kd /= ratio; | |
this._sampleTime = Math.round(value); | |
} | |
} | |
/** | |
* output( ) | |
* Set output level if in manual mode | |
*/ | |
set output(value) { | |
if (this._mode === PID.MANUAL) { | |
new Error("Can't set output value in manual mode"); | |
} | |
if (value > this.outMax) { | |
this._output = value; | |
} else if (value < this.outMin) { | |
value = this.outMin; | |
} | |
this._output = value; | |
} | |
/** | |
* outputLimits(...) | |
* Set the min and max range of the output | |
*/ | |
outputLimits(min, max) { | |
if (min >= max) { | |
return; | |
} | |
this.outMin = min; | |
this.outMax = max; | |
if (this._mode !== PID.MANUAL) { | |
if (this._output > this.outMax) { | |
this._output = this.outMax; | |
} else if (this._output < this.outMin) { | |
this._output = this.outMin; | |
} | |
if (this.iTerm > this.outMax) { | |
this.iTerm = this.outMax; | |
} else if (this.iTerm < this.outMin) { | |
this.iTerm = this.outMin; | |
} | |
} | |
} | |
/** | |
* SetMode(...) | |
* PID.DIRECT - Direct acting process ( +Output leads to +Input, e.g heating ) | |
* PID.REVERSE - Reverse acting process (+Output leads to -Input, e.g. Cooling ) | |
* PID.MANUAL - controller does not adjust output, application does (Override type situation) | |
*/ | |
set mode(m) { | |
this._mode = m; | |
// do all the things that need to happen to ensure a bumpless transfer from manual to automatic mode. | |
this.iTerm = this._output; | |
this.lastInput = this._input; | |
if (this.iTerm > this.outMax) { | |
this.iTerm = this.outMax; | |
} else if (this.iTerm < this.outMin) { | |
this.iTerm = this.outMin; | |
} | |
} | |
get Kp() { | |
return this.dispKp; | |
} | |
get Kd() { | |
return this.dispKd; | |
} | |
get Ki() { | |
return this.dispKi; | |
} | |
get mode() { | |
return this._mode; | |
} | |
get output() { | |
return this._output; | |
} | |
get input() { | |
return this._input; | |
} | |
get setpoint() { | |
return this._setpoint; | |
} | |
set input(current_value) { | |
this._input = current_value; | |
} | |
set setpoint(current_value) { | |
this._setpoint = current_value; | |
} | |
} | |
PID.MANUAL = 0; | |
PID.DIRECT = 1; | |
PID.REVERSE = -1; | |
export { | |
PID as | |
default, PID | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment