ПИД-регулятор, не запускается

Коллеги, добрый вечер! Извините, что пложу тему, нашел здесь на форуме еще один ПИД
https://support.wirenboard.com/t/pid-regulyator/16665
Не запускается, подскажите пожалуйста что подправить?


Ошибка звучит так:


SyntaxError: unterminated statement (line 343)
	duk_js_compiler.c:6227

Разобрался с запуском, в строке 330 нужно запятую убрать.
Подскажите пожалуйста, как сделать так, чтобы было несколько input’ов на вход ПИДа, и несколько выходов. А уставка и коэффиценты остались общие. Спасибо!

Насколько я понял, вам нужно реализовать несколько регуляторов с разными параметрами и одинаковыми коэффициентами.

Для добавления новых полей, если речь идет об имитации, необходимо добавить их в виртуальное устройство:

// Создаем виртуальное устройство
defineVirtualDevice("PIDTest", {
    title: "PID",
    cells: {
        // Топик температуры 1
        enabled1: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        // Топик температуры 2
        enabled2: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        // Уставка 1
        setPoint1: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        // Уставка 2
        setPoint2: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        // Вывод ПИД-а 1
        log1: {
            type: "text",
            value: ''
        },
         // Вывод ПИД-а 2
        log2: {
            type: "text",
            value: ''
        },
        // Коэф. пропорциональной составляющей
        kp1: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        // Коэф интегральной составляющей
      	ki1: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        }
    },
});

Далее реализовать функции вычисления для первого ПИД-а:

//цикл работы первого ПИД-а
var myControl1 = function() {
    // Актуализация (получение) параметров из панели
	var temperature = dev["PIDTest/enabled1"]; 
  	var temperatureSetpoint =  dev["PIDTest/setPoint1"]; 
  	var Kp = dev["PIDTest/kp1"],
        Ki = dev["PIDTest/ki1"],
    // Запись полученных/обновленных параметров в ПИД 	
  	ctr.setInput(temperature);
  	ctr.setPoint(temperatureSetpoint);
  	ctr.setTunings(Kp, Ki, Kd);
    // Работа ПИД-а
  	ctr.compute();
  	// Получение результата
  	var output = String(ctr.getOutput());
  	log("Output : " + output);

    dev["PIDTest/log1"] = output;
};
setInterval(myControl1, timeframe);

и второго:

//цикл работы второго ПИД-а
var myControl2 = function() {
    // Актуализация (получение) параметров из панели
	var temperature = dev["PIDTest/enabled2"]; 
  	var temperatureSetpoint =  dev["PIDTest/setPoint2"]; 
  	var Kp = dev["PIDTest/kp1"],
        Ki = dev["PIDTest/ki1"],
    // Запись полученных/обновленных параметров в ПИД 	
  	ctr.setInput(temperature);
  	ctr.setPoint(temperatureSetpoint);
  	ctr.setTunings(Kp, Ki, Kd);
    // Работа ПИД-а
  	ctr.compute();
  	// Получение результата
  	var output = String(ctr.getOutput());
  	log("Output : " + output);

    dev["PIDTest/log2"] = output;
};
setInterval(myControl2, timeframe);

Контроллера под рукой нет (код не проверял).

Спасибо, мою идею поняли правильно. А как оптимизировать, чтобы не плодить множество функций вычисления, можно ли просто вызывать функцию и в нее закидывать нужные аргументы (входную температуру, коэффиценты), а функция должна вернуть значение ПИД

И как подурутить пид, чтобы он работал в режиме “авто”. Т.е. есть уставка, и если ниже этой температуры - подогреть, если выше - охладить. (Значения ПИД, возвращаемые функцией, например от -minValue, в случае нагрева, до +maxValue в случае охлаждения, или на оборот)

// Функция для ПИД-а
var myControl = function(params) {
    // Актуализация (получение) параметров из панели
    var temperature = dev[params[0]]; 
    var temperatureSetpoint = dev[params[1]]; 
    var Kp = dev[params[2]],
        Ki = dev[params[3]],
        Kd = dev[params[4]];
    
    // Запись полученных/обновленных параметров в ПИД 	
    ctr.setInput(temperature);
    ctr.setPoint(temperatureSetpoint);
    ctr.setTunings(Kp, Ki, Kd);
    
    // Работа ПИД-а
    ctr.compute();
    
    // Получение результата
    var output = String(ctr.getOutput());
    log("Output : " + output);

    dev[params[5]] = output;
};

// Параметры для первого ПИД-а
var params1 = [
    "PIDTest/enabled1",
    "PIDTest/setPoint1",
    "PIDTest/kp1",
    "PIDTest/ki1",
    "PIDTest/kd1",
    "PIDTest/log1"
];

// Параметры для второго ПИД-а
var params2 = [
    "PIDTest/enabled2",
    "PIDTest/setPoint2",
    "PIDTest/kp2",
    "PIDTest/ki2",
    "PIDTest/kd2",
    "PIDTest/log2"
];

// Интервальные вызовы функций
setInterval(function() {
    myControl(params1);
}, timeframe);

setInterval(function() {
    myControl(params2);
}, timeframe);

Например так.
Для того, чтобы ПИД начал работать в обратную сторону, как я помню, необходимо поменять местами параметры “уставка” и “температура”.
Было:

  ctr.setInput(temperature);
  ctr.setPoint(temperatureSetpoint);

Стало:

  ctr.setInput(temperatureSetpoint);
  ctr.setPoint(temperature);

Можно обернуть все это в if, если одно стало больше другого.
Но скорее всего у вас будут два разных исполнительных устройства, один работать на охлаждение, другой на нагрев. Проще будет создать вызов нового ПИД-а.

// Параметры ПИД-а работающего на охлаждение
var params1 = [
   "PIDTest/enabled1",
   "PIDTest/setPoint1",
   "PIDTest/kp1",
   "PIDTest/ki1",
   "PIDTest/kd1",
   "PIDTest/log1"
];

// Параметры ПИД-а работающего на нагрев
var params2 = [
   "PIDTest/setPoint2",
   "PIDTest/enabled2",
//Первый и второй параметр переставлены местами
   "PIDTest/kp2",
   "PIDTest/ki2",
   "PIDTest/kd2",
   "PIDTest/log2"
];

Попробуйте так.

Для изменения чувствительности регулятора еще можно поиграть переменной timeframe

https://support.wirenboard.com/t/pid-regulyator-ne-zapuskaetsya/20743/3

Этот вариант не работает. Output 1 и Output2 -содержат одинаковые значения.

Да, извините. Обращение идет к одним и тем-же переменным вне функции.
Нужно обернуть все в класс и создать разные экземпляры. Вечером подправлю.

Спасибо! Уставка в идеале должна быть одна

Насколько я понял - классы не реализованы в этом диалекте js. Но они и не нужны.)

Вспомнил как работает.)
Для изменения направления ПИД-регулятора, в строке где происходит инициализация объекта, нужно указать параметр “reverse” или “direct”.
var ctr = new PID(temperature, temperatureSetpoint, Kp, Ki, Kd, 'reverse');

Перед тем как включать в работу второй ПИД, его сначала нужно объявить:

// Инициализация второго ПИД-регулятора
var temperature2 = dev["PIDTest/enabled2"];
var temperatureSetpoint2 =  dev["PIDTest/setPoint2"];
var Kp2 = dev["PIDTest/kp2"],
    Ki2 = dev["PIDTest/ki2"],
    Kd2 = 0;
var ctr2 = new PID(temperature2, temperatureSetpoint2, Kp2, Ki2, Kd2, 'direct');
ctr2.setSampleTime(timeframe);
ctr2.setOutputLimits(0, maxOutput);
ctr2.setMode('auto');
Код всего правила
var PID = function(Input, Setpoint, Kp, Ki, Kd, ControllerDirection) {
    this.input = Input;
    this.mySetpoint = Setpoint;
    this.inAuto = false;
    this.setOutputLimits(0, 255); // default output limits
    this.SampleTime = 100; // default Controller Sample Time is 0.1 seconds
    this.setTunings(Kp, Ki, Kd);
    this.setControllerDirection(ControllerDirection);
    this.lastTime = this.millis() - this.SampleTime;

    this.ITerm = 0;
    this.myOutput = 0;
};

PID.prototype.setInput = function(current_value) {
    this.input = current_value;
};

PID.prototype.setPoint = function(current_value) {
    this.mySetpoint = current_value;
};

PID.prototype.millis = function() {
    var d = new Date();
    return d.getTime();
};

PID.prototype.compute = function() {
    if (!this.inAuto) {
        return false;
    }
    var now = this.millis();
    var timeChange = (now - this.lastTime);
    if (timeChange >= this.SampleTime) {
        var input = this.input;
        var error = this.mySetpoint - input;
        this.ITerm += (this.ki * error);
        var dInput = input - this.lastInput;
        var output = (this.kp * error + this.ITerm - this.kd * dInput) * this.setDirection;

        if (output > this.outMax) {
            output = this.outMax;
        } else if (output < this.outMin) {
            output = this.outMin;
        }
        this.myOutput = output;

        this.lastInput = input;
        this.lastTime = now;
        return true;
    } else {
        return false;
    }
};

PID.prototype.setTunings = function(Kp, Ki, Kd) {
    if (Kp < 0 || Ki < 0 || Kd < 0) {
        return;
    }

    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;
};

PID.prototype.setSampleTime = function(NewSampleTime) {
    if (NewSampleTime > 0) {
        var ratio = NewSampleTime / (1.0 * this.SampleTime);
        this.ki *= ratio;
        this.kd /= ratio;
        this.SampleTime = Math.round(NewSampleTime);
    }
};

PID.prototype.setOutput = function(val) {
    if (val > this.outMax) {
        this.myOutput = val;
    } else if (val < this.outMin) {
        val = this.outMin;
    }
    this.myOutput = val;
};

PID.prototype.setOutputLimits = function(Min, Max) {
    if (Min >= Max) {
        return;
    }
    this.outMin = Min;
    this.outMax = Max;

    if (this.inAuto) {
        if (this.myOutput > this.outMax) {
            this.myOutput = this.outMax;
        } else if (this.myOutput < this.outMin) {
            this.myOutput = this.outMin;
        }

        if (this.ITerm > this.outMax) {
            this.ITerm = this.outMax;
        } else if (this.ITerm < this.outMin) {
            this.ITerm = this.outMin;
        }
    }
};

PID.prototype.setMode = function(Mode) {
    var newAuto;
    if (Mode == PID.AUTOMATIC || Mode.toString().toLowerCase() == 'automatic' || Mode.toString().toLowerCase() == 'auto') {
        newAuto = 1;
    } else if (Mode == PID.MANUAL || Mode.toString().toLowerCase() == 'manual') {
        newAuto = 0;
    } else {
        throw new Error("Incorrect Mode Chosen");
    }

    if (newAuto == !this.inAuto) {
        this.initialize();
    }
    this.inAuto = newAuto;
};

PID.prototype.initialize = function() {
    this.ITerm = this.myOutput;
    this.lastInput = this.input;
    if (this.ITerm > this.outMax) {
        this.ITerm = this.outMax;
    } else if (this.ITerm < this.outMin) {
        this.ITerm = this.outMin;
    }
};

PID.prototype.setControllerDirection = function(ControllerDirection) {
    if (ControllerDirection == 0 || ControllerDirection.toString().toLowerCase() == 'direct') {
        this.setDirection = 1;
    } else if (ControllerDirection == 1 || ControllerDirection.toString().toLowerCase() == 'reverse') {
        this.setDirection = -1;
    } else {
        throw new Error("Incorrect Controller Direction Chosen");
    }
};

PID.prototype.getKp = function() {
    return this.dispKp;
};

PID.prototype.getKd = function() {
    return this.dispKd;
};

PID.prototype.getKi = function() {
    return this.dispKi;
};

PID.prototype.getMode = function() {
    return this.inAuto ? "Auto" : "Manual";
};

PID.prototype.getDirection = function() {
    return this.controllerDirection;
};

PID.prototype.getOutput = function() {
    return this.myOutput;
};

PID.prototype.getInput = function() {
    return this.input;
};

PID.prototype.getSetPoint = function() {
    return this.mySetpoint;
};

module.exports = PID;

// Создаем виртуальное устройство с несколькими ПИД-регуляторами
defineVirtualDevice("PIDTest", {
    title: "PID",
    cells: {
        enabled1: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        setPoint1: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        log1: {
            type: "text",
            value: ''
        },
        kp1: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        ki1: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        },
        enabled2: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        setPoint2: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        log2: {
            type: "text",
            value: ''
        },
        kp2: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        ki2: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        }
    },
});

// Время вызова
var timeframe = 2000;
var maxOutput = 10000;

// Инициализация первого ПИД-регулятора
var temperature1 = dev["PIDTest/enabled1"];
var temperatureSetpoint1 =  dev["PIDTest/setPoint1"];
var Kp1 = dev["PIDTest/kp1"],
    Ki1 = dev["PIDTest/ki1"],
    Kd1 = 0;
var ctr1 = new PID(temperature1, temperatureSetpoint1, Kp1, Ki1, Kd1, 'direct');
ctr1.setSampleTime(timeframe);
ctr1.setOutputLimits(0, maxOutput);
ctr1.setMode('auto');

// Инициализация второго ПИД-регулятора
var temperature2 = dev["PIDTest/enabled2"];
var temperatureSetpoint2 =  dev["PIDTest/setPoint2"];
var Kp2 = dev["PIDTest/kp2"],
    Ki2 = dev["PIDTest/ki2"],
    Kd2 = 0;
var ctr2 = new PID(temperature2, temperatureSetpoint2, Kp2, Ki2, Kd2, 'direct');
ctr2.setSampleTime(timeframe);
ctr2.setOutputLimits(0, maxOutput);
ctr2.setMode('auto');

//цикл работы ПИД-а
var myControl = function() {
    // Обработка первого ПИД-регулятора
    var temperature1 = dev["PIDTest/enabled1"]; 
    var temperatureSetpoint1 =  dev["PIDTest/setPoint1"]; 
    var Kp1 = dev["PIDTest/kp1"],
        Ki1 = dev["PIDTest/ki1"];
    ctr1.setInput(temperature1);
    ctr1.setPoint(temperatureSetpoint1);
    ctr1.setTunings(Kp1, Ki1, Kd1);
    ctr1.compute();
    var output1 = ctr1.getOutput();
    log("Output1 : " + output1);
    dev["PIDTest/log1"] = String(output1);

    // Обработка второго ПИД-регулятора
    var temperature2 = dev["PIDTest/enabled2"]; 
    var temperatureSetpoint2 =  dev["PIDTest/setPoint2"]; 
    var Kp2 = dev["PIDTest/kp2"],
        Ki2 = dev["PIDTest/ki2"];
    ctr2.setInput(temperature2);
    ctr2.setPoint(temperatureSetpoint2);
    ctr2.setTunings(Kp2, Ki2, Kd2);
    ctr2.compute();
    var output2 = ctr2.getOutput();
    log("Output2 : " + output2);
    dev["PIDTest/log2"] = String(output2);
};
setInterval(myControl, timeframe);

Забыл про это требование
var PID = function(Input, Setpoint, Kp, Ki, Kd, ControllerDirection) {
    this.input = Input;
    this.mySetpoint = Setpoint;
    this.inAuto = false;
    this.setOutputLimits(0, 255); // default output limits
    this.SampleTime = 100; // default Controller Sample Time is 0.1 seconds
    this.setTunings(Kp, Ki, Kd);
    this.setControllerDirection(ControllerDirection);
    this.lastTime = this.millis() - this.SampleTime;

    this.ITerm = 0;
    this.myOutput = 0;
};

PID.prototype.setInput = function(current_value) {
    this.input = current_value;
};

PID.prototype.setPoint = function(current_value) {
    this.mySetpoint = current_value;
};

PID.prototype.millis = function() {
    var d = new Date();
    return d.getTime();
};

PID.prototype.compute = function() {
    if (!this.inAuto) {
        return false;
    }
    var now = this.millis();
    var timeChange = (now - this.lastTime);
    if (timeChange >= this.SampleTime) {
        var input = this.input;
        var error = this.mySetpoint - input;
        this.ITerm += (this.ki * error);
        var dInput = input - this.lastInput;
        var output = (this.kp * error + this.ITerm - this.kd * dInput) * this.setDirection;

        if (output > this.outMax) {
            output = this.outMax;
        } else if (output < this.outMin) {
            output = this.outMin;
        }
        this.myOutput = output;

        this.lastInput = input;
        this.lastTime = now;
        return true;
    } else {
        return false;
    }
};

PID.prototype.setTunings = function(Kp, Ki, Kd) {
    if (Kp < 0 || Ki < 0 || Kd < 0) {
        return;
    }

    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;
};

PID.prototype.setSampleTime = function(NewSampleTime) {
    if (NewSampleTime > 0) {
        var ratio = NewSampleTime / (1.0 * this.SampleTime);
        this.ki *= ratio;
        this.kd /= ratio;
        this.SampleTime = Math.round(NewSampleTime);
    }
};

PID.prototype.setOutput = function(val) {
    if (val > this.outMax) {
        this.myOutput = val;
    } else if (val < this.outMin) {
        val = this.outMin;
    }
    this.myOutput = val;
};

PID.prototype.setOutputLimits = function(Min, Max) {
    if (Min >= Max) {
        return;
    }
    this.outMin = Min;
    this.outMax = Max;

    if (this.inAuto) {
        if (this.myOutput > this.outMax) {
            this.myOutput = this.outMax;
        } else if (this.myOutput < this.outMin) {
            this.myOutput = this.outMin;
        }

        if (this.ITerm > this.outMax) {
            this.ITerm = this.outMax;
        } else if (this.ITerm < this.outMin) {
            this.ITerm = this.outMin;
        }
    }
};

PID.prototype.setMode = function(Mode) {
    var newAuto;
    if (Mode == PID.AUTOMATIC || Mode.toString().toLowerCase() == 'automatic' || Mode.toString().toLowerCase() == 'auto') {
        newAuto = 1;
    } else if (Mode == PID.MANUAL || Mode.toString().toLowerCase() == 'manual') {
        newAuto = 0;
    } else {
        throw new Error("Incorrect Mode Chosen");
    }

    if (newAuto == !this.inAuto) {
        this.initialize();
    }
    this.inAuto = newAuto;
};

PID.prototype.initialize = function() {
    this.ITerm = this.myOutput;
    this.lastInput = this.input;
    if (this.ITerm > this.outMax) {
        this.ITerm = this.outMax;
    } else if (this.ITerm < this.outMin) {
        this.ITerm = this.outMin;
    }
};

PID.prototype.setControllerDirection = function(ControllerDirection) {
    if (ControllerDirection == 0 || ControllerDirection.toString().toLowerCase() == 'direct') {
        this.setDirection = 1;
    } else if (ControllerDirection == 1 || ControllerDirection.toString().toLowerCase() == 'reverse') {
        this.setDirection = -1;
    } else {
        throw new Error("Incorrect Controller Direction Chosen");
    }
};

PID.prototype.getKp = function() {
    return this.dispKp;
};

PID.prototype.getKd = function() {
    return this.dispKd;
};

PID.prototype.getKi = function() {
    return this.dispKi;
};

PID.prototype.getMode = function() {
    return this.inAuto ? "Auto" : "Manual";
};

PID.prototype.getDirection = function() {
    return this.controllerDirection;
};

PID.prototype.getOutput = function() {
    return this.myOutput;
};

PID.prototype.getInput = function() {
    return this.input;
};

PID.prototype.getSetPoint = function() {
    return this.mySetpoint;
};

module.exports = PID;

// Создаем виртуальное устройство с несколькими ПИД-регуляторами
defineVirtualDevice("PIDTest", {
    title: "PID",
    cells: {
        enabled1: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        setPoint: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        log1: {
            type: "text",
            value: ''
        },
        kp1: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        ki1: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        },
        enabled2: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        log2: {
            type: "text",
            value: ''
        },
        kp2: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        ki2: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        }
    },
});

// Время вызова
var timeframe = 2000;
var maxOutput = 10000;

// Инициализация первого ПИД-регулятора
var temperature1 = dev["PIDTest/enabled1"];
var temperaturesetPoint =  dev["PIDTest/setPoint"];
var Kp1 = dev["PIDTest/kp1"],
    Ki1 = dev["PIDTest/ki1"],
    Kd1 = 0;
var ctr1 = new PID(temperature1, temperaturesetPoint, Kp1, Ki1, Kd1, 'direct');
ctr1.setSampleTime(timeframe);
ctr1.setOutputLimits(0, maxOutput);
ctr1.setMode('auto');

// Инициализация второго ПИД-регулятора
var temperature2 = dev["PIDTest/enabled2"];
var temperaturesetPoint =  dev["PIDTest/setPoint"];
var Kp2 = dev["PIDTest/kp2"],
    Ki2 = dev["PIDTest/ki2"],
    Kd2 = 0;
var ctr2 = new PID(temperature2, temperaturesetPoint, Kp2, Ki2, Kd2, 'reverse');
ctr2.setSampleTime(timeframe);
ctr2.setOutputLimits(0, maxOutput);
ctr2.setMode('auto');

//цикл работы ПИД-а
var myControl = function() {
    // Обработка первого ПИД-регулятора
    var temperature1 = dev["PIDTest/enabled1"]; 
    var temperaturesetPoint =  dev["PIDTest/setPoint"]; 
    var Kp1 = dev["PIDTest/kp1"],
        Ki1 = dev["PIDTest/ki1"];
    ctr1.setInput(temperature1);
    ctr1.setPoint(temperaturesetPoint);
    ctr1.setTunings(Kp1, Ki1, Kd1);
    ctr1.compute();
    var output1 = ctr1.getOutput();
    log("Output1 : " + output1);
    dev["PIDTest/log1"] = String(output1);

    // Обработка второго ПИД-регулятора
    var temperature2 = dev["PIDTest/enabled2"]; 
    var temperaturesetPoint =  dev["PIDTest/setPoint"]; 
    var Kp2 = dev["PIDTest/kp2"],
        Ki2 = dev["PIDTest/ki2"];
    ctr2.setInput(temperature2);
    ctr2.setPoint(temperaturesetPoint);
    ctr2.setTunings(Kp2, Ki2, Kd2);
    ctr2.compute();
    var output2 = ctr2.getOutput();
    log("Output2 : " + output2);
    dev["PIDTest/log2"] = String(output2);
};
setInterval(myControl, timeframe);

Прошу прощения, наверное не совсем в тему но все же спрошу.
Возможно ли этот регулятор изменить для работы по поддержанию уровня СО на необходимом уровне? Есть датчик СО и частотный преобразователь у вентилятора. Посмотрев на простыню кода, понял, что никогда в жизни не осилю трансформацию текущего вашего регулятора.

Эту часть кода просто копируете
var PID = function(Input, Setpoint, Kp, Ki, Kd, ControllerDirection) {
    this.input = Input;
    this.mySetpoint = Setpoint;
    this.inAuto = false;
    this.setOutputLimits(0, 255); // default output limits
    this.SampleTime = 100; // default Controller Sample Time is 0.1 seconds
    this.setTunings(Kp, Ki, Kd);
    this.setControllerDirection(ControllerDirection);
    this.lastTime = this.millis() - this.SampleTime;

    this.ITerm = 0;
    this.myOutput = 0;
};

// Constants for backward compatibility
PID.AUTOMATIC = 1;
PID.MANUAL = 0;
PID.DIRECT = 1;
PID.REVERSE = 1;

PID.prototype.setInput = function(current_value) {
    this.input = current_value;
};

PID.prototype.setPoint = function(current_value) {
    this.mySetpoint = current_value;
};

PID.prototype.millis = function() {
    var d = new Date();
    return d.getTime();
};

/**
 * Compute()
 * This, as they say, is where the magic happens.  this function should be called
 * every time "void loop()" executes.  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.
 */
PID.prototype.compute = function() {
    if (!this.inAuto) {
        return false;
    }
    var now = this.millis();
    var timeChange = (now - this.lastTime);
    if (timeChange >= this.SampleTime) {

        // Compute all the working error variables
        var input = this.input;
        var error = this.mySetpoint - input;
        this.ITerm += (this.ki * error);
        var dInput = input - this.lastInput;
        // Compute PID Output
        var output = (this.kp * error + this.ITerm - this.kd * dInput) * this.setDirection;

        if (output > this.outMax) {
            output = this.outMax;
        }
        else if (output < this.outMin) {
            output = this.outMin;
        }
        this.myOutput = output;

        // Remember some variables for next time
        this.lastInput = input;
        this.lastTime = now;
        return true;
    }
    else {
        return false;
    }
};

/**
 * SetTunings(...)
 * 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
 */
PID.prototype.setTunings = function(Kp, Ki, Kd) {
    if (Kp < 0 || Ki < 0 || Kd < 0) {
        return;
    }

    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;
};

/**
 * SetSampleTime(...)
 * sets the period, in Milliseconds, at which the calculation is performed	
 */
PID.prototype.setSampleTime = function(NewSampleTime) {
    if (NewSampleTime > 0) {
        var ratio = NewSampleTime / (1.0 * this.SampleTime);
        this.ki *= ratio;
        this.kd /= ratio;
        this.SampleTime = Math.round(NewSampleTime);
    }
};

/**
 * SetOutput( )
 * Set output level if in manual mode
 */
PID.prototype.setOutput = function(val) {
    if (val > this.outMax) {
        this.myOutput = val;
    }
    else if (val < this.outMin) {
        val = this.outMin;
    }
    this.myOutput = val;
};

/**
 * SetOutputLimits(...)
 * This function will be used far more often than SetInputLimits.  while
 * the input to the controller will generally be in the 0-1023 range (which is
 * the default already,)  the output will be a little different.  maybe they'll
 * be doing a time window and will need 0-8000 or something.  or maybe they'll
 * want to clamp it from 0-125.  who knows.  at any rate, that can all be done here.
 */
PID.prototype.setOutputLimits = function(Min, Max) {
    if (Min >= Max) {
        return;
    }
    this.outMin = Min;
    this.outMax = Max;

    if (this.inAuto) {
        if (this.myOutput > this.outMax) {
            this.myOutput = this.outMax;
        }
        else if (this.myOutput < this.outMin) {
            this.myOutput = this.outMin;
        }

        if (this.ITerm > this.outMax) {
            this.ITerm = this.outMax;
        }
        else if (this.ITerm < this.outMin) {
            this.ITerm = this.outMin;
        }
    }
};

/**
 * SetMode(...)
 * Allows the controller Mode to be set to manual (0) or Automatic (non-zero)
 * when the transition from manual to auto occurs, the controller is
 * automatically initialized
 */
PID.prototype.setMode = function(Mode) {
    var newAuto;
    if (Mode == PID.AUTOMATIC || Mode.toString().toLowerCase() == 'automatic' || Mode.toString().toLowerCase() == 'auto') {
        newAuto = 1;
    }
    else if (Mode == PID.MANUAL || Mode.toString().toLowerCase() == 'manual') {
        newAuto = 0;
    }
    else {
        throw new Error("Incorrect Mode Chosen");
    }

    if (newAuto == !this.inAuto) { //we just went from manual to auto
        this.initialize();
    }
    this.inAuto = newAuto;
};

/**
 * Initialize()
 * does all the things that need to happen to ensure a bumpless transfer
 * from manual to automatic mode.
 */
PID.prototype.initialize = function() {
    this.ITerm = this.myOutput;
    this.lastInput = this.input;
    if (this.ITerm > this.outMax) {
        this.ITerm = this.outMax;
    }
    else if (this.ITerm < this.outMin) {
        this.ITerm = this.outMin;
    }
};

/**
 * SetControllerDirection(...)
 * The PID will either be connected to a DIRECT acting process (+Output leads 
 * to +Input) or a REVERSE acting process(+Output leads to -Input.)  we need to
 * know which one, because otherwise we may increase the output when we should
 * be decreasing.  This is called from the constructor.
 */
PID.prototype.setControllerDirection = function(ControllerDirection) {
    if (ControllerDirection == 0 || ControllerDirection.toString().toLowerCase() == 'direct') {
        this.setDirection = 1;
    }
    else if (ControllerDirection == 1 || ControllerDirection.toString().toLowerCase() == 'reverse') {
        this.setDirection = -1;
    }
    else {
        throw new Error("Incorrect Controller Direction Chosen");
    }
};

/**
 * Status Functions
 * Just because you set the Kp=-1 doesn't mean it actually happened.  these
 * functions query the internal state of the PID.  they're here for display 
 * purposes.  this are the functions the PID Front-end uses for example
 */
PID.prototype.getKp = function() {
    return this.dispKp;
};

PID.prototype.getKd = function() {
    return this.dispKd;
};

PID.prototype.getKi = function() {
    return this.dispKi;
};

PID.prototype.getMode = function() {
    return this.inAuto ? "Auto" : "Manual";
};

PID.prototype.getDirection = function() {
    return this.controllerDirection;
};

PID.prototype.getOutput = function() {
    return this.myOutput;
};

PID.prototype.getInput = function() {
    return this.input;
};

PID.prototype.getSetPoint = function() {
    return this.mySetpoint;
};

module.exports = PID;
Создаете виртуальное устройство для изменения коэффициентов, уставки и контроля работы пида
// Создаем виртуальное устройство
defineVirtualDevice("PIDTest", {
    title: "PID",
    cells: {
        // Уставка
        setPoint: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        // Вывод ПИД-а
        log1: {
            type: "text",
            value: ''
        },
        // Коэф. пропорциональной составляющей
        kp1: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        // Коэф интегральной составляющей
      	ki1: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        }
    },
});
Немного по-трансформировать:
// Время вызова
var timeframe = 2000
// Максимальное значение выхода ПИД-а
var maxOutput = 10000;

// Инициализация ПИД-а
var temperature = dev["PIDTest/enabled"];
var temperatureSetpoint =  dev["PIDTest/setPoint"];
var Kp = dev["PIDTest/kp1"],
    Ki = dev["PIDTest/ki1"],
    Kd = 0;
// direct - работа на обогрев, reverse - на охлаждение
var ctr = new PID(temperature, temperatureSetpoint, Kp, Ki, Kd, 'reverse');
ctr.setSampleTime(timeframe);
ctr.setOutputLimits(0, maxOutput);
ctr.setMode('auto');

//цикл работы ПИД-а
var myControl = function() {
    // Актуализация (получение) параметров из панели
	var temperature = dev["PIDTest/enabled"]; 
  	var temperatureSetpoint =  dev["PIDTest/setPoint"]; 
  	var Kp = dev["PIDTest/kp1"],
        Ki = dev["PIDTest/ki1"];
    // Запись полученных/обновленных параметров в ПИД 	
  	ctr.setInput(temperature);
  	ctr.setPoint(temperatureSetpoint);
  	ctr.setTunings(Kp, Ki, Kd);
    // Работа ПИД-а
  	ctr.compute();
  	// Получение результата
  	var output = String(ctr.getOutput());
  	log("Output : " + output);

    dev["PIDTest/log1"] = output;
};
setInterval(myControl, timeframe);

Временем вызова можно изменять скорость приращения, если Ki и Kp будет недостаточно.

Максимальное значение пида - максимальное значение для передачи в частотник.

var temperature = dev[“PIDTest/enabled”]; - меняете на топик СО.

// direct - работа на обогрев, reverse - на охлаждение
var ctr = new PID(temperature, temperatureSetpoint, Kp, Ki, Kd, ‘reverse’); - выставляете в какую сторону работать пид-у

По идее этого достаточно.

p.s.
В виртуальном устройстве измените диапазон для СО

// Уставка
        setPoint: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
3 лайка

Смотрю в ваш код и он приносит радость. Респект.

2 лайка

Огромное спасибо, буду пробовать!

В связи с чем может ругаться на строку
var ctr = new PID(temperature, temperatureSetpoint, Kp, Ki, Kd, ‘reverse’);
?

ERROR

TypeError: not constructable
duk_api_call.c:396
F /etc/wb-rules/rules.js:53 preventsyield

чтобы “погонять” в тесте добавил виртуальное значение для temperature

    // Значение
    enabled: {
        type: "range",
        value: 20,
        min: 15,
        max: 40 
    },

Пришлите весь листинг

rules.js (9,1 КБ)

.js не дает доступа скачать, пришлите архивом