kotelna

Odpovědět
Uživatelský avatar
PetrSmetana
Příspěvky: 311
Registrován: pon 22. črc 2019 9:06:45
Bydliště: Mnich
Dal poděkování: 28 poděkování
Dostal poděkování: 60 poděkování
Kontaktovat uživatele:

kotelna

Příspěvek od PetrSmetana » ned 10. pro 2023 22:35:51

Tento víkend jsem konečně vykopl drobnou automatizaci do kotelny, která může v rámci charakteru kotelny přerůst v plnohodnotný řídící systém.

HW
V kotelně mám automatický kotel dakon na uhlí/pelety. Ten je přes čerpadlo připojen na nátopnou nádrž.
V nátopné nádrži mám 27 litrový výměník na ohřev pitné vody.
V létě natápím 6kW dražickou patronou pro ohřev pitné vody.
Nádrž je napojena na topný okruh přes čerpadlo a směšovací ventil.

Aktuální stav
Natápění si řidí kotel (teplota výstupní vody, teplota spalin, posun paliva, řízení přívodu vzduchu) - teplotu, na kterou kotel topí je možno řídit externě přes 0-10V
Letní natápění nádrže provádím na patroně ručně (v časech spínání HDO)
směšovací ventil není připojený
radiátory jsou osazeny termoregulačními hlavicemi s centrálním řízením
topný okruh má čerpadlo s automatickou detekcí stavu okruhu - od toho se odvíjí čerpaný objem
nátopná nádrž je připravená na další zdroje - solární/termický ohřev - tepelné čerpadlo
celý systém má kopici objímek na čidla teploty (osazeno analogovými budíky) a je tam i čidlo na tlak

Je patrné, že možnosti co a jak řídit je nespočet. Otopnou soustavu jsem si stavěl svépomocí. Aktuálně bezproblémově funguje, ale rád bych postupně zvýšil uživatelský komfort a pomocí ekvitermního řízení optimalizoval spotřebu

Prvním krokem bylo vymyslet nějaký malý projekt a konečně v kotelně RPi osadit.

Projekt č. 1 - hlídání množství uhlí v násypce kotle
Dlouhodobě jsem řešil možnosti hlídání hladiny uhlí v násypce. První co mě napadlo byl ultrazvukový senzor vzdálenosti, ale kvůli prašnosti prostředí by to bylo neschůdné. Pak mě napadlo násypku vážit, ale to by bylo technicky špatně proveditelné. Nakonec mě napadlo snímat otáčky šnekového posuvníku, který dopravuje palivo z násypky do hořáku.

Taktika byla jednoduchá - zjistit počet otáček do vyprázdnění násypky (v tomto stavu aktuálně jsem) a následně zaslat zprávu na telegram když bude obsah v násepce 15% a po doplnění násypky vynulovat čítač.

Oprášil jsem starou RPi 1B a dal se do práce.

Nejdřív jsem počítal s halovou sondou a snímáním rotujícího magnetu na hřídeli - nebyl jsem však spokojen s přesností, tak jsem nakonec použil mikrospínač a na hřídel jsem dal kousek železa, který na něj při otáčení mačká (hřídel je snadno dostupná pod plastovým krytem, do kterého jsem spínač zadělal). Zároveň jsem použil čidlo BMP180 na snímání venkovní teploty přes I2C.

Pro kódování jsem použil python, a každou hodinu odsypávám "log" datum/čas teplota počet otáček bokem.

Hodnotu teploty snímám vždy s "otáčkou" - obě hodnoty sypu do souboru do /dev/shm (ramdisk) ať neždímám SD kartu.

Resetovat čítač otáček a získávat aktuální status mohu přes telegram příkazy /status a /reset

Co dál - rád bych postupně dodělal notifikaci na doplnění násypky a pak bych chtěl řešit nějakou jednoduchou vizualizaci - statický web na rpi s vykreslováním grafů přes javascript (mám doma nějaké extra old androidí tablety, které by mohly fungovat jako stavové displeje+resetovač). Postupně dodám fotky a finální zdroják. Pokud máte nějaké nápady/návrhy, rád si je vyslechnu!!! Níže přikládám, aktuální zdroják - neni to nikterak úchvatné, ale funguje to :)

Kód: Vybrat vše

#!/usr/bin/env python

from time import sleep
import RPi.GPIO as GPIO
import smbus
from ctypes import c_short
import requests
import telepot
from telepot.loop import MessageLoop


def send_msg(text):
   token = "<TELEGRAM_TOKEN>"
   chat_id = "<CHAT_ID>"
   url_req = "https://api.telegram.org/bot" + token + "/sendMessage" + "?chat_id=" + chat_id + "&text=" + text
   results = requests.get(url_req)


def handle(msg):
    chat_id = msg['chat']['id']
    command = msg['text']

    print(command)

    if command == '/reset':
        bot.sendMessage(chat_id, "nuluji citac")
        f = open(OTACKY, "w")
        f.write("0")
        f.close()
    elif command == '/status':
        fo = open(OTACKY, "r")
        ot=str(fo.read())
        ft = open(TEPLOTA, "r")
        te=str(ft.read())
        bot.sendMessage(chat_id,"teplota: "+te+" otacky: "+ ot)

def convertToString(data):
  # Simple function to convert binary data into
  # a string
  return str((data[1] + (256 * data[0])) / 1.2)

def getShort(data, index):
  # return two bytes from data as a signed 16-bit value
  return c_short((data[index] << 8) + data[index + 1]).value

def getUshort(data, index):
  # return two bytes from data as an unsigned 16-bit value
  return (data[index] << 8) + data[index + 1]

def readBmp180Id(addr=DEVICE):
  # Chip ID Register Address
  REG_ID     = 0xD0
  (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
  return (chip_id, chip_version)

def readBmp180(addr=0x77):
  # Register Addresses
  REG_CALIB  = 0xAA
  REG_MEAS   = 0xF4
  REG_MSB    = 0xF6
  REG_LSB    = 0xF7
  # Control Register Address
  CRV_TEMP   = 0x2E
  CRV_PRES   = 0x34
  # Oversample setting
  OVERSAMPLE = 3    # 0 - 3

  # Read calibration data
  # Read calibration data from EEPROM
  cal = bus.read_i2c_block_data(addr, REG_CALIB, 22)

  # Convert byte data to word values
    AC1 = getShort(cal, 0)
  AC2 = getShort(cal, 2)
  AC3 = getShort(cal, 4)
  AC4 = getUshort(cal, 6)
  AC5 = getUshort(cal, 8)
  AC6 = getUshort(cal, 10)
  B1  = getShort(cal, 12)
  B2  = getShort(cal, 14)
  MB  = getShort(cal, 16)
  MC  = getShort(cal, 18)
  MD  = getShort(cal, 20)

  # Read temperature
  bus.write_byte_data(addr, REG_MEAS, CRV_TEMP)
  sleep(0.005)
  (msb, lsb) = bus.read_i2c_block_data(addr, REG_MSB, 2)
  UT = (msb << 8) + lsb

  # Read pressure
  bus.write_byte_data(addr, REG_MEAS, CRV_PRES + (OVERSAMPLE << 6))
  sleep(0.04)
  (msb, lsb, xsb) = bus.read_i2c_block_data(addr, REG_MSB, 3)
  UP = ((msb << 16) + (lsb << 8) + xsb) >> (8 - OVERSAMPLE)

  # Refine temperature
  X1 = ((UT - AC6) * AC5) >> 15
  X2 = (MC << 11) / (X1 + MD)
  B5 = X1 + X2
  temperature = int(B5 + 8) >> 4
  temperature = temperature / 10.0

  # Refine pressure
  B6  = B5 - 4000
  B62 = int(B6 * B6) >> 12
  X1  = (B2 * B62) >> 11
  X2  = int(AC2 * B6) >> 11
    X3  = X1 + X2
  B3  = (((AC1 * 4 + X3) << OVERSAMPLE) + 2) >> 2

  X1 = int(AC3 * B6) >> 13
  X2 = (B1 * B62) >> 16
  X3 = ((X1 + X2) + 2) >> 2
  B4 = (AC4 * (X3 + 32768)) >> 15
  B7 = (UP - B3) * (50000 >> OVERSAMPLE)

  P = (B7 * 2) / B4

  X1 = (int(P) >> 8) * (int(P) >> 8)
  X1 = (X1 * 3038) >> 16
  X2 = int(-7357 * P) >> 16
  pressure = int(P + ((X1 + X2 + 3791) >> 4))
  #pressure = float(pressure / 100.0)


  altitude = 44330.0 * (1.0 - pow(pressure / 101325.0, (1.0/5.255)))
  altitude = round(altitude,2)

  return (temperature,pressure,altitude)


send_msg("startuji kotelnika")
bot = telepot.Bot(<TELEGRAM_TOKEN>)
MessageLoop(bot, handle).run_as_thread()

GPIO.setmode(GPIO.BCM)
INPUT_PIN = 22
GPIO.setup(INPUT_PIN, GPIO.IN)

OTOCENI=GPIO.input(INPUT_PIN)
OTACKY="/dev/shm/otacky"
TEPLOTA="/dev/shm/teplota"

f = open(OTACKY, "w")
f.write("0")
f.close()

DEVICE = 0x77 # Default device I2C address

#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1

temp, pressure, altitude = readBmp180()
f = open(TEPLOTA, "w")
f.write(str(temp))
f.close()

while True:

           if (GPIO.input(INPUT_PIN) == True):
                    OTOCENI=True
           else:
                    if (OTOCENI == True):
                        f = open(OTACKY, "r")
                        o=str(int(f.read())+1)
                        f = open(OTACKY, "w")
                        f.write(o)
                        f.close()
                        temp, pressure, altitude = readBmp180()
                        f = open(TEPLOTA, "w")
                        f.write(str(temp))
                        f.close()
                        print(str(temp))
                        print(o)
                        OTOCENI = False
           sleep(0.3)
        
Modré z nebe na počkání, zázraky do dvou dnů.
Uživatelský avatar
PetrSmetana
Příspěvky: 311
Registrován: pon 22. črc 2019 9:06:45
Bydliště: Mnich
Dal poděkování: 28 poděkování
Dostal poděkování: 60 poděkování
Kontaktovat uživatele:

Re: kotelna

Příspěvek od PetrSmetana » sob 23. pro 2023 8:59:03

Přidán celkový počet otáček (POCET_OTACEK) - pro upozornění při 15% zbývajícího paliva

pridan prikaz /help do telegramu (vypise vsechny prikazy)

/status - zjisti aktualni stav
/history - vypise historii dat 10h zpet
/set <CISLO> - nastavi aktualni pocet otacek
/reset - vynuluje citac otacek

Aktuálně připravuji vizualizaci přes lighttpd

Největším nepřítelem jsou mi zatím výpadky elektrického proudu ... přemýšlím o zálohování napájení.


Kód: Vybrat vše

#!/usr/bin/env python

from time import sleep
import RPi.GPIO as GPIO
import smbus
from ctypes import c_short
import requests
import telepot
import os
from telepot.loop import MessageLoop



POCET_OTACEK=760
OTACKY_ALERT=round(POCET_OTACEK/100*85)
print(OTACKY_ALERT)

GPIO.setmode(GPIO.BCM)
INPUT_PIN = 22
GPIO.setup(INPUT_PIN, GPIO.IN)

OTOCENI=GPIO.input(INPUT_PIN)
OTACKY="/dev/shm/otacky"
TEPLOTA="/dev/shm/teplota"

f = open(OTACKY, "w")
f.write("0")
f.close()

DEVICE = 0x77 # Default device I2C address
 
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1 

def send_msg(text):
   token = "<TELEGRAM_TOKEN>"
   chat_id = "<CHAT_ID>"
   url_req = "https://api.telegram.org/bot" + token + "/sendMessage" + "?chat_id=" + chat_id + "&text=" + text 
   results = requests.get(url_req)


def handle(msg):
    chat_id = msg['chat']['id']
    command = msg['text']

    print(command)

    scom=command.split(' ')

    if command == '/reset':
        bot.sendMessage(chat_id, "nuluji citac")
        f = open(OTACKY, "w")
        f.write("0")
        f.close()
    elif command == '/status':
        fo = open(OTACKY, "r")
        ot=str(fo.read())
        ft = open(TEPLOTA, "r")
        te=str(ft.read())
        bot.sendMessage(chat_id,"teplota: "+te+" otacky: "+ ot)
    elif command == '/history':
        history=os.popen('tail /var/log/kotelnik').read();
        bot.sendMessage(chat_id,history)
    elif scom[0] == '/set':
        f = open(OTACKY, "w")
        f.write(scom[1])
        f.close()
        ft = open(TEPLOTA, "r")
        te=str(ft.read())
        bot.sendMessage(chat_id,"pocet otacek nastaven")
    elif command == '/help':
        bot.sendMessage(chat_id,"/status - zjisti aktualni stav\n/history - vypise historii dat 10h zpet\n/set <CISLO> - nastavi aktualni pocet otacek\n/reset - vynuluje citac otacek")


 
def convertToString(data):
  # Simple function to convert binary data into
  # a string
  return str((data[1] + (256 * data[0])) / 1.2)

def getShort(data, index):
  # return two bytes from data as a signed 16-bit value
  return c_short((data[index] << 8) + data[index + 1]).value

def getUshort(data, index):
  # return two bytes from data as an unsigned 16-bit value
  return (data[index] << 8) + data[index + 1]

def readBmp180Id(addr=DEVICE):
  # Chip ID Register Address
  REG_ID     = 0xD0
  (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
  return (chip_id, chip_version)
  
def readBmp180(addr=0x77):
  # Register Addresses
  REG_CALIB  = 0xAA
  REG_MEAS   = 0xF4
  REG_MSB    = 0xF6
  REG_LSB    = 0xF7
  # Control Register Address
  CRV_TEMP   = 0x2E
  CRV_PRES   = 0x34 
  # Oversample setting
  OVERSAMPLE = 3    # 0 - 3
  
  # Read calibration data
  # Read calibration data from EEPROM
  cal = bus.read_i2c_block_data(addr, REG_CALIB, 22)

  # Convert byte data to word values
  AC1 = getShort(cal, 0)
  AC2 = getShort(cal, 2)
  AC3 = getShort(cal, 4)
  AC4 = getUshort(cal, 6)
  AC5 = getUshort(cal, 8)
  AC6 = getUshort(cal, 10)
  B1  = getShort(cal, 12)
  B2  = getShort(cal, 14)
  MB  = getShort(cal, 16)
  MC  = getShort(cal, 18)
  MD  = getShort(cal, 20)

  # Read temperature
  bus.write_byte_data(addr, REG_MEAS, CRV_TEMP)
  sleep(0.005)
  (msb, lsb) = bus.read_i2c_block_data(addr, REG_MSB, 2)
  UT = (msb << 8) + lsb

  # Read pressure
  bus.write_byte_data(addr, REG_MEAS, CRV_PRES + (OVERSAMPLE << 6))
  sleep(0.04)
  (msb, lsb, xsb) = bus.read_i2c_block_data(addr, REG_MSB, 3)
  UP = ((msb << 16) + (lsb << 8) + xsb) >> (8 - OVERSAMPLE)

  # Refine temperature
  X1 = ((UT - AC6) * AC5) >> 15
  X2 = (MC << 11) / (X1 + MD)
  B5 = X1 + X2
  temperature = int(B5 + 8) >> 4
  temperature = temperature / 10.0

  # Refine pressure
  B6  = B5 - 4000
  B62 = int(B6 * B6) >> 12
  X1  = (B2 * B62) >> 11
  X2  = int(AC2 * B6) >> 11
  X3  = X1 + X2
  B3  = (((AC1 * 4 + X3) << OVERSAMPLE) + 2) >> 2

  X1 = int(AC3 * B6) >> 13
  X2 = (B1 * B62) >> 16
  X3 = ((X1 + X2) + 2) >> 2
  B4 = (AC4 * (X3 + 32768)) >> 15
  B7 = (UP - B3) * (50000 >> OVERSAMPLE)

  P = (B7 * 2) / B4

  X1 = (int(P) >> 8) * (int(P) >> 8)
  X1 = (X1 * 3038) >> 16
  X2 = int(-7357 * P) >> 16
  pressure = int(P + ((X1 + X2 + 3791) >> 4))
  #pressure = float(pressure / 100.0)
  
  
  altitude = 44330.0 * (1.0 - pow(pressure / 101325.0, (1.0/5.255)))
  altitude = round(altitude,2)

  return (temperature,pressure,altitude)





send_msg("startuji kotelnika")
bot = telepot.Bot('<TELEGRAM_TOKEN>')
MessageLoop(bot, handle).run_as_thread()


temp, pressure, altitude = readBmp180()
f = open(TEPLOTA, "w")
f.write(str(temp))
f.close()

while True:

           if (GPIO.input(INPUT_PIN) == True):
                    OTOCENI=True
           else:
                    if (OTOCENI == True):
                        f = open(OTACKY, "r")
                        o=str(int(f.read())+1)
                        if(OTACKY_ALERT == int(o)):
                            send_msg("Zbyva 15 procent uhli, je treba dosypat!")
                        f = open(OTACKY, "w")
                        f.write(o)
                        f.close()
                        temp, pressure, altitude = readBmp180()
                        f = open(TEPLOTA, "w")
                        f.write(str(temp))
                        f.close()
                        print(str(temp))
                        print(o)
                        OTOCENI = False
           sleep(0.3)
Modré z nebe na počkání, zázraky do dvou dnů.
Odpovědět