Skip to content

Instantly share code, notes, and snippets.

@wilberforce
Last active August 1, 2019 07:12
Show Gist options
  • Save wilberforce/88231ea867bc190199c33538c039201f to your computer and use it in GitHub Desktop.
Save wilberforce/88231ea867bc190199c33538c039201f to your computer and use it in GitHub Desktop.
pid-controller for moddable
/*
* 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
};
{
"include": [
"$(MODDABLE)/examples/manifest_base.json",
],
"modules": {
"*": "./main",
"PID": "./pid"
},
}
/*
* 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