Fazendo um simulador de controlador on-off de JavaScript

A essência da teoria do controle automático implica na construção de um sistema que mantenha um determinado parâmetro de algum objeto em um determinado estado, por exemplo, a temperatura em uma fornalha ou o nível de água em um tanque. Para uma melhor compreensão do processo, é conveniente considerar imediatamente um modelo de controle específico, digamos, controlar o nível de água em um tanque. A propósito, em livros didáticos e artigos sobre TAU, esse processo é frequentemente mencionado como uma referência à história, porque no distante 1763 o inventor russo I.I. desenvolveu um sistema de controle de nível de água para sua máquina a vapor. Uma espécie de regulador clássico, que, aliás, é essencialmente um regulador de duas posições como nesta foto (não há água - abra a válvula, há água feche a válvula).







É duas posições porque tem 2 posições: aberto (ligado) e fechado (desligado), na literatura de língua inglesa liga-desliga. Existem também três ou mais reguladores posicionais, ou seja, a válvula de reposição de água é aberta ou fechada para as posições principais, e é adicionada a posição "ligeiramente aberta". Após escoar a água do vaso sanitário, a bóia desce abrindo a válvula completamente e a água entra no tanque com pressão total, porém, mais perto de atingir o nível ajustado, a bóia sobe fechando a válvula e diminuindo o fluxo de água. E assim que o nível de água atual (em inglês PV - Process Value - Current value ) subir para o conjunto (em inglês SP - Set Point - Setpoint), a válvula fecha e o nível de água para de subir. No caso descrito, o regulador é ainda mais parecido com o proporcional - a ação reguladora diminui com a diminuição do mismatch (erro), ou seja, a diferença entre o nível ajustado e o nível atual.



Abrindo ligeiramente o tubo inferior para drenar a água, será possível atingir tal estado quando a válvula estiver totalmente aberta e o nível de água não diminuir (ou seja, o influxo de água torna-se igual à fonte) - o sistema entra em um estado de equilíbrio. Mas o problema é que esse estado é muito precário - qualquer perturbação externa pode quebrar esse equilíbrio - digamos que podemos pegar um pouco de água do tanque, e então pode acontecer que toda a água flua para fora do tanque (devido à mudança de pressão), ou o tubo de recarga ficará entupido e o fluxo diminuirá, ou a bóia quebrará e a água transbordará. Essa é a complexidade dos sistemas de controle de edifícios - sistemas reais são bastante complexos e têm muitas características que precisam ser levadas em consideração.Existe uma característica como a inércia do sistema - se desligar o recuperador aquecido, ele permanecerá quente por muito tempo, razão pela qual se utilizam reguladores mais complexos para controlar a temperatura, nomeadamentePID - Diferencial Integral Proporcional . Cada um dos componentes tem suas próprias características - todos eles se comportam de maneira diferente em condições diferentes, mas quando usados ​​juntos, eles permitem alcançar uma regulação bastante clara. Todos esses sistemas são calculados de acordo com as fórmulas, mas, neste caso, é apenas importante entender como o sistema se comportará quando os coeficientes do controlador PID mudarem: com um aumento no link proporcional, o impacto inicial aumenta e, assim, o sistema será capaz de atingir rapidamente os parâmetros necessários. Mas, se você exagerar, poderá ocorrer ultrapassagem, o que pode ser ainda pior do que a baixa velocidade do sistema.



Durante a existência do TAU, foram encontradas descrições matemáticas de muitos processos, e agora podemos prever como o sistema se comportará em determinadas circunstâncias. Existem muitos programas de simulação onde você pode definir os parâmetros do sistema, definir os parâmetros do regulador e ver aproximadamente o que vai acontecer. Andando pela Internet, encontrei um site Excel para engenheiros, e existem vários simuladores de reguladores, graças aos quais é possível observar a mudança no processo ao mudar os fatores de controle. O mais fácil de repetir era, claro, o controle ON-OFF., isto é, em russo, um regulador de duas posições. Deixe-me lembrar o princípio de operação: se o valor atual do processo (Valor do processo = PV) é a temperatura, por exemplo, abaixo do setpoint (SP), então o regulador liga (OP) - os elementos de aquecimento são acionados na potência máxima. Assim que a temperatura atinge o ponto de ajuste, o regulador desliga a alimentação de tensão para os elementos de aquecimento.



Fazendo um simulador de JavaScript



Para construir um gráfico, usarei a biblioteca ZingChart - ela acabou sendo bastante simples e fácil de usar. Existem muitos exemplos na documentação para os quais você pode construir qualquer coisa. O princípio de plotagem é bastante simples - há uma matriz de valores que são colocados automaticamente no gráfico em ordem e, portanto, um gráfico de processo contínuo aparece a partir de algumas centenas de pontos. Aliás, no original em Excel, tudo é feito da mesma forma - 300 valores são gerados e um gráfico é construído.



Na verdade, é a geração de valores que é mais difícil, nomeadamente a dificuldade de descrever corretamente um processo que reage corretamente às nossas ações de controle - ligar os elementos de aquecimento - a temperatura sobe, desligar - diminui, mais a inércia do sistema deve ser colocada aqui. Além disso, o ambiente de aquecimento pode ser diferente e alguns meios aquecem e resfriam mais rápido, e alguns vice-versa, e se ajustarmos o nível, com o mesmo fluxo de cima, o nível aumentará mais alto no tanque onde a área inferior é menor. Tudo isso levo ao fato de que o processo vai depender também da transmissão (ganho). No original, um parâmetro de atraso também foi introduzido no processo (bem, como o sistema não responde imediatamente ao sinal de controle), mas decidi abandoná-lo - dois são suficientes. Mas ele acrescentou uma mudança na configuração,embora na verdade o ponto de ajuste possa mudar de zero a 100, acima de 100 o processo começa a se comportar de maneira diferente e, aparentemente, a razão é que a fórmula do processo é universal e não descreve um caso particular. Em geral, vamos começar:



Criamos 5 campos para entrada de parâmetros, colocamos tudo isso em uma tabela, que pintamos em uma bela cor acima em css e colocamos no centro:



<table align="center" oninput="setvalues ();">
	<tr>
	<td>
Process parameters <br>
Gain: <input id="gain" type="number" value ="1" ><br>
Time Constant: <input id="time" type="number" value ="100" ><br>
	</td>
	<td>
Control parameters <br>
SetPoint(0-100): <input id="sp" type="number" value ="50"><br>
Hysteresis: <input id="hyst" type="number" value ="1">%<br>
	</td>
	<td>
Plot parameters <br>	
Points: <input id="points" type="number" value ="200"><br>
	</td>
	</tr>
</table>


Como você pode ver, cada vez que o valor dos campos dentro da tabela muda, a função setvalues ​​() será chamada. Nele, lemos os dados de cada campo em variáveis ​​especiais.



	let gain = document.getElementById('gain').value;
	let time = document.getElementById('time').value;
	let sp = document.getElementById('sp').value;
	let points = document.getElementById('points').value;
	let hyst = document.getElementById('hyst').value;


Como já mencionado, para construir um gráfico, você precisa de arrays com dados com base nos quais o gráfico será construído, portanto, criamos um monte de arrays:



let pv = []; //    
let pv100 = []; //   *100
let op = []; //   1 , 0 
let pvp = 0; //  
let low = sp-sp*hyst/100;//  
let high = +sp+(sp*hyst/100); //   
let st=true; //  


Vou explicar um pouco sobre a histerese. A situação é a seguinte: quando a temperatura atinge o valor definido, os elementos de aquecimento são desligados e imediatamente (na verdade, não imediatamente, porque há inércia) começa o processo de refrigeração. E tendo resfriado um grau ou mesmo uma certa fração de grau - o sistema percebe que já ultrapassou o escopo da tarefa novamente e é necessário ligar os elementos de aquecimento novamente. Neste modo, os elementos de aquecimento irão ligar e desligar com muita frequência, talvez até algo que várias vezes por minuto - para equipamentos este modo não é muito bom e, portanto, para excluir tais flutuações, é introduzida a chamada histerese - banda morta - banda morta - digamos 1 grau superior e abaixo do ponto de ajuste, não reagiremos, e então o número de comutações pode ser reduzido significativamente. Portanto, a variável low é o limite inferior do setpoint, e a variável high é o superior.A variável st mantém o controle de atingir o nível superior e permite que o processo caia para o fundo. A lógica de todo o processo está em loop:



	for (var i=0;i<points;i++) {
		if (pvp<=(low/100)) {
			st=true;
			op[i]=1;
			}//
		else if (pvp<=(high/100)&& st) op[i] = 1;
		else { st=false; op[i]=0;}
		
		let a = Math.pow(2.71828182845904, -1/time);
		let b = gain*(1 -a);
		pv[i] = op[i]*b+pvp*a;
		pv100[i] = pv[i]*100;
		pvp = pv[i];
	}


Como resultado, obtemos um array com um determinado número de pontos, que enviamos para o script de gráficos.



scaleX: {
 	zooming: true
  },
      series: [
		{ values: op , text: 'OP' },
        { values: pv100 , text: 'PV'}
      ]
    };


Código completo sob o spoiler
<!DOCTYPE html>
<html>
 
<head>
  <meta charset="utf-8">
  <title></title>
 
  <script src="https://cdn.zingchart.com/zingchart.min.js"></script>
  <style>
    html,
    body,
    #myChart {
      width: 100%;
      height: 100%;
    }
	input {
	width: 25%;
	text-align:center;
	}
	td {
	
	background-color: peachpuff;
	text-align: center;
	}	
  </style>
</head>
<body>
<table align="center" oninput="setvalues ();">
	<tr>
	<td>
Process parameters <br>
Gain: <input id="gain" type="number" value ="1" ><br>
Time Constant: <input id="time" type="number" value ="100" ><br>
	</td>
	<td>
Control parameters <br>
SetPoint(0-100): <input id="sp" type="number" value ="50"><br>
Hysteresis: <input id="hyst" type="number" value ="2">%<br>
	</td>
	<td>
Plot parameters <br>	
Points: <input id="points" type="number" value ="250"><br>
Animation: <input type="checkbox" id="animation">
	</td>
	</tr>
</table>

<script>

setTimeout('setvalues ()', 0);

function setvalues (){

	let gain = document.getElementById('gain').value;
	let time = document.getElementById('time').value;
	let sp = document.getElementById('sp').value;
	let points = document.getElementById('points').value;
	let hyst = document.getElementById('hyst').value;
	let anim = document.getElementById('animation').checked ? +1 : 0;
	let pv = []; //    
	let pv100 = []; //   *100
	let op = []; //   1 , 0 
	let pvp = 0; //  
	let low = sp-sp*hyst/100; //  
	let high = +sp+(sp*hyst/100); //  
	let st=true; //  
	for (var i=0;i<points;i++) {
		if (pvp<=(low/100)) {
			st=true;
			op[i]=1;
			}
		else if (pvp<=(high/100)&& st) op[i] = 1;
		else { st=false; op[i]=0;}
		
		let a = Math.pow(2.71828182845904, -1/time);
		let b = gain*(1 -a);
		pv[i] = op[i]*b+pvp*a;
		pv100[i] = pv[i]*100;
		pvp = pv[i];
	}
	
	ZC.LICENSE = ["569d52cefae586f634c54f86dc99e6a9", "b55b025e438fa8a98e32482b5f768ff5"];
    var myConfig = {
    type: "line",
    "plot": {
		"animation": {
          "effect": anim,
          "sequence": 2,
          "speed": 200,
        }
		},
	legend: {
    layout: "1x2", //row x column
    x: "20%",
    y: "5%",
	},
 	crosshairX:{
 	  plotLabel:{
 	    text: "%v"
 	  }
 	},
      "scale-y": {
    item: {
      fontColor: "#7CA82B"
    },
    markers: [
	 {
        type: "area",
        range: [low, high],
        backgroundColor: "#d89108",
        alpha: 0.7
      },
	{
        type: "line",
        range: [sp],
        lineColor: "#7CA82B",
        lineWidth: 2,
		  label: { //define label within marker
          text: "SP = "+sp,
          backgroundColor: "white",
          alpha: 0.7,
          textAlpha: 1,
          offsetX: 60,
          offsetY: -5
        }
      }]
	},	
	scaleX: {
		zooming: true
	},
	  'scale-y-2': {
	  values: "0:1"
	},
      series: [
		{ scales: "scale-x,scale-y-2", values: op , 'legend-text': 'OP' },
        { values: pv100 , text: 'PV'}
      ]
    };
 
    zingchart.render({
      id: 'myChart',
      data: myConfig,
      height: "90%",
      width: "100%"
    });
}


</script>
  <div id='myChart'></div>
</body> 
</html>




Bem, como o simulador está pronto, é hora de verificar como funciona. Você pode testar aqui o mesmo código, mas no github: simulador de controle liga-desliga



Configuração padrão: link amplificador 1, constante de tempo 100 segundos, histerese 2%







Agora, se você definir uma configuração maior, por exemplo 92, de repente o processo fica muito lento, embora a configuração seja 50 ele ganha nos mesmos 71 segundos, mas só então a curva começa a se aproximar da tarefa de forma mais lenta exponencialmente, e atinge o setpoint em apenas 278 segundos, por isso foi necessário expandir a faixa de plotagem para 300 pontos







Este exemplo é muito indicativo, transferindo a situação para um modelo com temperatura, podemos concluir que não há potência do aquecedor suficiente: o aquecedor está 100% carregado, mas a temperatura para de subir após um determinado momento. Pode haver várias soluções: coloque um segundo elemento de aquecimento do mesmo, ou aplique tensão nele 2 vezes mais (mas isso pode danificar o elemento de aquecimento), ou coloque um aquecedor com 2 vezes mais potência, ou despeje um líquido mais condutor de calor no sistema quando se trata de aquecimento líquidos. É bastante interessante que se você precisa manter a temperatura na região de 95-100 graus, então você não precisa nem colocar o regulador - coloque um aquecedor de baixa potência, corte-o ao máximo e pronto - após 300 segundos (300 segundos condicionais) você pode obter os 100 graus desejados.O problema com esse sistema é que se você abrir uma janela no inverno a menos de 40 graus, a temperatura cairá imediatamente e de forma bastante significativa, e o desempenho de tal sistema é muito baixo.



Vamos aumentar a seção de ganho em 2 vezes - é como instalar um segundo elemento de aquecimento do mesmo tipo ou adicionar outro tubo para reabastecer o tanque.







O gráfico acabou sendo também bastante indicativo - a temperatura atingiu 51 graus na verdade, chegou a 2 vezes mais rápido, mas chegou a 92 graus 4 vezes mais rápido. Não sei o quão próximo esse simulador está de processos reais, mas como a dependência especificada nele é exponencial, este é um comportamento completamente esperado do sistema, mas não consigo nem imaginar explicar da perspectiva de adicionar um segundo tubo e aumentar a taxa de enchimento em 4 vezes. A resposta de uma função linear seria mais previsível a um aumento no coeficiente, mas os sistemas reais raramente são lineares.



All Articles