Украл в интернете софтверный термостат по алгоритму TPI

С Новым Годом!

Искал, искал по сайту более-менее нормальный термостат на основе правил, но не смог разбораться и полез искать на английском. Нашел про алгоритм TPI и другие, даже книжку на 100 страниц нашел, ничего не понял, естественно, но попался мне код на неизвестном мне языке правил openhab, и решил переделать как всегда по-своему. Результат ниже, неизвестно, по-факту, работает он или нет, так как мои реле еще ни к чему не подключены, но делюсь, может, кому-то подаст идею.

Единственное что я еще очень хочу захачить, это управление котлом. У меня, как у всех, комнат много, а котел один, соответственно, если все поставят себе разные температуры, то хотелось бы, чтобы алгоритм делал котел потише или погромче, в зависимости от потребностей жителей и способности каждого из термостатов это обеспечить.

У меня теплые полы с водой и батареи, а на гребенках стоят клапаны Valtec, которые вроде бы открываются и закрываются по несколько минут.

Значит, TPI, насколько я понял, это алгоритм, высчитывающий, насколько нужно подключать отопление, чтобы оно в комнате сначала достигло требуемой уставки, а потом держалось очень близко к ней, чего “простой термостат”, даже с поправкой на “гистерезис” обеспечить не может, поскольку TPI якобы умеет подклчать отопление заранее, не давая фактической температуре упасть. Он действует по принципу ШИМа, то есть, у него есть какой-то (в нашем случае - десятиминутный) цикл, в течение которого он решает, на сколько процентов от этих 10 минут в систему будет подаваться горячая вода, и сколько она там будет стыть.

Как оно на самом деле работать будет, еще не проверял, но вдруг есть идеи или замечания заранее, то почитаю!

Остальные умные слова по ссылкам выше, а вот код (вроде работает). Вначале массив определений настроек термостатов, в каждом их которых мы указываем термометр и реле, которое управляет клапаном.

С праздником еще раз!

var thermostats = {
    fl2_east_toilet_thermostat: {
        name: "Nick's Toilet Thermostat",
        thermometer: "fl2_east_toilet_hum_temp/temperature",
        relay: "wb-gpio/EXT3_R3A7",
        nc: true, // if true, switching the relay on opens the valve, if false - closes
        // coefficients to tweak. Can be moved to the virtual device
        proportionalGain: 1.3,
        integralGain: 0.02,
        cycle: 10 * 60 * 1000 // ten minutes
    },
    fl1_toilet_thermostat: {
        name: "Main Toilet Thermostat",
        thermometer: "fl1_toilet_hum_temp/temperature",
        relay: "wb-mio-gpio_147:1/K4",
        nc: true, // if true, switching the relay on opens the valve, if false - closes
        // coefficients to tweak
        proportionalGain: 1.3,
        integralGain: 0.02,
        cycle: 10 * 60 * 1000 // ten minutes
    }
};

for (var thermostat in thermostats) {
    thermostats[thermostat].max = 40;
    thermostats[thermostat].min = 15;
    thermostats[thermostat].loopTimer = null;
    thermostats[thermostat].heatTimer = null;
    if (!("nc" in thermostats[thermostat])) {
        thermostats[thermostat].nc = true;
    }
}

var adjust = function (setPoint, thermostat) {
    // TPI majik as in https://community.openhab.org/t/tpi-thermostat-rule/114879
    var currentTemp = dev[thermostats[thermostat].thermometer];
    var proportionalError = ((setPoint - currentTemp) * thermostats[thermostat].proportionalGain);
    var oldIntegralError = dev[thermostat + "/integralError"];
    if (setPoint > currentTemp) {
        dev[thermostat + "/integralError"] =
                oldIntegralError +
                ((setPoint - currentTemp) * thermostats[thermostat].integralGain);
        log("Update Integral Error from {} to {} in thermostat {}",
                oldIntegralError, dev[thermostat + "/integralError"], thermostat);
    }
    var error = proportionalError + dev[thermostat + "/integralError"];
    if (error > 0.2) {
        if (thermostats[thermostat].heatTimer !== null) {
            clearTimeout(thermostats[thermostat].heatTimer);
        }
        dev[thermostats[thermostat].relay] = thermostats[thermostat].nc; // open the valve
        if (error > 0.9) {
            error = 1;
        }
        thermostats[thermostat].heatTimer = setTimeout(function () {
            dev[thermostats[thermostat].relay] = !thermostats[thermostat].nc; // close the valve
        }, error * thermostats[thermostat].cycle);
    }
};



var thermostatRule = function (thermostat) {
    return {
        whenChanged: thermostat + "/setPoint",
        then: function (setPoint) {
            if (thermostats[thermostat].loopTimer !== null) {
                clearTimeout(thermostats[thermostat].loopTimer);
                thermostats[thermostat].loopTimer = null;
            }

            if (thermostats[thermostat].heatTimer !== null) {
                clearTimeout(thermostats[thermostat].heatTimer);
                thermostats[thermostat].heatTimer = null;
            }
            dev[thermostats[thermostat].relay] = !thermostats[thermostat].nc; // close the valve
            dev[thermostat + "/integralError"] = 0.0; // start all over

            log("Reset thermostat: {}, Integral Error: {}", thermostat, dev[thermostat + "/integralError"]);

            if (setPoint >= thermostats[thermostat].min) {

                thermostats[thermostat].loopTimer = setInterval(function () {
                    adjust(setPoint, thermostat); // run immediately
                }, thermostats[thermostat].cycle); // every ten minutes

                adjust(setPoint, thermostat); // run immediately

            }

        }
    };
};

for (var thermostat in thermostats) {

    defineVirtualDevice(thermostat, {
        title: thermostats[thermostat].name,
        cells: {
            integralError: {
                title: "Integral Error",
                type: "value",
                value: 0,
                readonly: true
            },
            setPoint: {
                title: "Desired Temperature",
                type: "range",
                value: thermostats[thermostat].min,
                max: thermostats[thermostat].max,
                min: thermostats[thermostat].min
            }
        }
    });

    defineRule(thermostat + '_logic', thermostatRule(thermostat));

}

У меня в доме реализовано управление радиаторами, ТП и котлом. Я поигрался разными алгоритмами, все описал в статье. Смысла пересказывать нет, почитайте:

1 лайк

Спасибо! Очень интересная и большая статья, боюсь не все понять =) У вас написано, что термоэлектрические клапаны управляются устройством “МУ110-16К от ОВЕН”, только я не понял, сами они работают от 220 или 24В (на сайте вроде написано, что они и так и так могут, вот и спрашиваю, как по факту). У меня-то вайренбордовские реле, есть “твердотельные”, а есть “магнитные” на 220. Вот, помните, мы считали, что для питания 24В мне понадобится много/большие блоки питания для 16 клапанов, поэтому я решил управлять 220-вольтовыми реле. А вы ниже про теплые полы вы пишете, что для “ПИД-регулирования” вы применяете “твердотельные” реле, потому что импульсы идут постоянно. Уж не стоит ли мне для теплых полов-таки вернуться к 24 вольтам и купить-таки блоки питания?.. В общем, буду думать и считать денюшки

На теплых полах термоголовки на 24В, на радиаторах - на 230В. На радиаторах были встроенные термостатические вентили от Danfoss, а подходящих термоголовок на 24В я купить не смог, а перекручивать вентили не хотелось. Те, что на 230В, подключил ч-з твердотельные реле.

У моих сервоприводов ток невелик, даже при пуске, так что БП на 60 Вт мне хватило.

Используйте для управления сервоприводами на 230В твердотельные реле. Механических реле, даже если использовать управление по гистерезису, хватит ненадолго.

Моя схема подключения:

У вас-то MD-0544.ZD3, а я назвал “твердотельным” WBIO-DO-SSR-8. Оно в документации вообще называется “оптореле” и там не разрешают 230В коммутировать через него… Мне-таки стоит докупить настоящие “твердотельные” реле? (или рискнуть 220 через ssr-8 пропустить)

Безусловно, WBIO-DO-SSR-8 не коммутирует 230В, в документации же написано. Но вы на выход SSR-8 можете подключить ТТР, и уже ими коммутировать 230В.

Спасибо. Кажется, по деньгам одинаково будет, если я куплю 2 таких “зональных коммуникатора” родных той же фирмы, что и мои сервоприводы. Полагаю, внутри них как раз твердотельные реле нужных характеристик. Только места уже нет в щитке, я думал одними реле от WB отделаться =)

edit: сдается мне, нет в этой штуке никаких реле (кроме одного - для насоса), он расчитывает на реле термостата, и ток на выключатель будет подан те же 220В. За что 7 тыщ непонятно =(

А можно отдельный топик создам для обсуждения твердотельных реле для управления сервоприводами? Я вроде поискал другие топики “реле для теплого пола”, и другие из WirenBoard Team, кажется, говорят, что и Модуль релейных выходов 1А для контакторов WBIO-DO-R1G-16 — Wiren Board подойдет, и Модуль релейных выходов 7A (WBIO-DO-R10A-8) — Wiren Board. Я понимаю, что если нет движущихся частей, то оно и долговечнее будет, но получается такой огород в щитке!

А там, может, наведем на мысль сделать модуль wirenboard с этими волшебными твердотельными реле, чтобы было удобно управлять теплыми полами, и чтобы долговечно было

Любые релейные модули подойдут, если коммутируемые напряжение/ток будут в рабочем диапазоне. Вопрос в ресурсе.
А топик создавайте, почему нет. Пожелания наших клиентов нам интересны.