ベランダビオトープ棚作り(4) システム

GWから作り始めているベランダビオトープ棚、システムを早く組まねばーっと思ってまごまごしてたら、お古のバッテリーとチャージコントローラーを手に入れてしまいました。

ソーラーパネルのみ(蓄電装置なし)で暑いときだけ、ファン・ポンプを稼働させるつもりでしたが、手に入れたからには蓄電込みのシステムにしようと思い、現状で、構想通りのシステムはまだ組めていませんが、最低限の動作はしているので、記事にします。

 

■システム

◇構成

ATOM LiteをMicroPythonで制御して以下の装置を動作させています。

・温度センサ×1
・水検知センサ×1
・水ポンプ×1
・ファン×1

 

◇動作

・バッテリー電圧を監視し、過放電防止
・ファンとポンプはPWM制御でパワーコントロール
・ポンプ稼働時は水センサで水位確認し、空運転防止(フロースイッチ)
・WIFIで時刻を取得し、ポンプをスケジュール稼働
・外気温によってファンのパワー調整
・稼働状況をWEBサーバーにロギング
・サーバーからプログラム更新可能

 

■プログラム

◇サーバーからのプログラム更新

一定時間ごとに、サーバーに新規プログラムがないか確認して、あればプログラムを更新するようにしてます。(詳細はこちら
main.pyからprogram.pyのmain関数をループし、一定間隔で更新プログラムがあるか確認します。

main.py

import machine,neopixel
import network
import urequests
import utime

np = neopixel.NeoPixel(machine.Pin(27, machine.Pin.OUT), 1)
np[0] = (10, 0, 0) #red
np.write()

#URLs for the updates
upd_url = "http://hogehoge/get_script.php?file=program.py"
del_url = "http://hogehoge/delete_script.php?file=program.py"

def connect_wifi():
    print('connecting to network...',end=' ')
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        sta_if.active(True)
        sta_if.connect('SSID','PASSWORD') #要変更
        st = utime.time()
        while not sta_if.isconnected():
            if utime.time()-st > 10: #timeout
                print('cannot connect wifi')
                return False
            utime.sleep(0.1)
        print('connected!')
    else:
        print('already connecting')
    #print('network config:', sta_if.ifconfig())
    return True

def check_for_updates():
    print('Checking if the file has been updated...')
    if not connect_wifi():
        return False
    try:
        response = urequests.get(upd_url)
        x = response.text.strip()
        if x == "FAIL":
            print('no updates available.')
            return False
        else:
            print('update available')
            return True
    except:
        print('unable to reach internet')
        return False

def program_update():
    print('Downloading update files...')
    #download the update and overwrite program.py
    response = urequests.get(upd_url)
    x = response.text.strip()
    if x != "FAIL":
        #download twice and compare for security
        x = response.text
        response = urequests.get(upd_url)
        if response.text == x:
            f = open("program.py","w")
            f.write(response.text)
            f.flush()
            f.close()
            print('done')
            urequests.get(del_url)
            
            print('reboot now')
            machine.deepsleep(5000)

def main():
    program_error = 0
    while True:
        if check_for_updates():
            program_update()
            program_error = 0
        try:
            import program
            program.main()
        except:
            print('PROGRAM RUN ERROR')
            np[0] = (10, 10, 0) #yellow
            np.write()
            utime.sleep(60)

if __name__ == '__main__':
    main()

 

◇時間外ではスリープ

時刻を取得して、9時〜18時以外はディープスリープします。

import time

def set_localtime():
    import ntptime
    utime.sleep(1) # wait connect to NTPserver
    ntptime.settime()
    tm = utime.localtime(utime.time()+9*3600)
    return tm

def main(looptime=1800):
    print('start program')
    global stopped_mainloop
    import main
    pwm2.duty(0) #for error
    wifi_connected = False
    if main.connect_wifi():
        log_data()
        tm = set_localtime()
        tm_hour = tm[3]
        tm_min = tm[4]
        wifi_connected = True
    else:
        tm_hour = 12
        tm_min = 0

    if tm_hour < 9 or tm_hour > 18:
	print('time is off-duty. start deepsleep...')
	for gp in [19,21,22,23,25,26,32,33,34]:
		machine.Pin(gp).init(pull=None)
	np[0] = (0, 0, 0)
	np.write()
	machine.deepsleep(looptime*1000)

なお、deepsleepに入る前にPinをリセットしてるのは、回路によってはプルアップ抵抗による電力消費を節約するためだとかで、なんとなく書いています。

 

◇外気温とバッテリー電圧のロギング

稼働した時に外気温と電圧をサーバーにアップします。
アップ先は、自前のWEBサービスを利用。このグラフを眺めるだけで面白い。
温度センサーについてはこちら

import machine

adc0 = machine.ADC(machine.Pin(33))
adc0.atten(machine.ADC.ATTN_11DB)
adc0_factor = 3.6 / 65535 * 5.5
def get_batt_vol(): 
    return adc0.read_u16()*adc0_factor

adc1 = machine.ADC(machine.Pin(32))
adc1.atten(machine.ADC.ATTN_2_5DB)
def get_temp():
    val = 0.0
    for i in range(100):
        val += adc1.read()
        utime.sleep(0.001)
    val = val/100
    vol =  val * 1.40/4095+0.03
    temp = (vol - 0.6)*100
    return temp

def log_data(append_data=''):
    batt_vol = get_batt_vol()
    temp = get_temp()
    url = 'http://webapps.tiblab.net/device/alive-monitor/?p=input-XXXXXXXX'
    data = '&d1='+str(batt_vol)+'&d2='+str(temp)+append_data
    ret = urequests.get(url+data)
    if ret.text[:1] == '1':
        print('success send data:'+data)
        return True
    else:
        print('fail send data:'+data)
        return False
 
 def main(looptime=1800):
    print('start program')
    global stopped_mainloop
    import main
    wifi_connected = False
    if main.connect_wifi():
        log_data()
        #omit

 

◇ファンのパワーコントロール

ファンとポンプのコントロールは、別にスレッドを立てて、マルチスレッド方式にしました。それが必須の事由はないのですが、その方がコードを分離できて書きやすくなるので(不具合が起きた時のデバッグがしづらくなるかもでずが)。

ファンは、PWM制御で、外気温が35℃以上で40%のパワーで回り始め、温度が1℃上がるごとに10%パワーを上げるようにしました。

ついでにファンの稼働状況をサーバーにアップしてます。

import machine

pwm1 = machine.PWM(machine.Pin(25))
pwm1.duty(0)

def fan_control():
    global stopped_fan_control
    print('start fan control')
    is_spinning = False
    while True:
        batt_vol =  get_batt_vol()
        temp = get_temp()
        if not is_spinning:
            if batt_vol > 13.0 and temp > 35:
                power = min(max((temp-35.0)*0.1,0.4),1.0)
                pwm1.duty(int(1023*power))
                log_data('&d3='+str(power))
                is_spinning = True
                print('fan start')
        else:
            if batt_vol < 12.0 or temp < 35:
                pwm1.duty(0)
                log_data('&d3=0.0')
                is_spinning = False
                print('fan stop')
       
        if stopped_mainloop:
            break
        utime.sleep(1)
        
    stopped_fan_control = True
    print('stopped fan control')

stopped_mainloop = False
def main(looptime=1800):
    print('start mainloop')
    start_time = utime.time()
    while True:

        #main task
        
        if(utime.time()-start_time > looptime):
            stopped_mainloop = True
            break
    print('stopped mainloop')
    
    print('waiting thread stop')
    while True:
        if stopped_fan_control:
            break
        utime.sleep(0.1)
    print('fin')

 

◇ポンプの時間稼働

ポンプは、朝と夕方の2回、5分間稼働させてます。PWM制御でパワーは必要最低限になるように、何回か試して5%にしました。

ポンプは水がない状態で使用すると故障するらしいので、水検知センサにより空運転を防止してます。
水センサについてはこちら

やってみて分かった課題としては、指定した時刻の時に動くようにしましたが、インターネット回線が意外と不安定で、その5分間が接続できていないと時刻が取得できずポンプが稼働しません。
なので、稼働したかどうかのログをサーバーに残して、未実施なら実施する処理が要るなと思いました。

stopped_pump_cont = True
def pump_control():
    global stopped_pump_cont
    print('start pump control')
    stopped_pump_cont = False
    while True:
        tm = utime.localtime(utime.time()+9*3600)
        tm_hour = tm[3]
        tm_min = tm[4]
        if ((tm_hour==10 or tm_hour==17) and tm_min < 5 and water_pin.value()):
            pwm0.duty(int(1023*0.05))
        else:
            pwm0.duty(0)
        
        if stopped_mainloop:
            break
        utime.sleep(1)
    stopped_pump_cont = True
    print('stopped pump control')

 

プログラム全体、回路図とかは次回以降。。。^^;

 

構想としては、温度センサと水検知センサ、ファンを1つずつ追加する予定です。

懸念点としては、バッテリー電圧の推移を見ると、晴れの日が続いていても電圧は下降の一途で、十分な充電ができていないこと。

なんで、2週間に一回程度、フル充電させる必要があります。
んー、パネルは壁に立てかけているだけで場所はよくなんだと思いますが、直射日光は当たる場所だし、ファン(3W)はほとんど回っていないし、ポンプ(5W)は一日10分回ってる時間はそんなに長くないのに、20Wのソーラーパネルで賄えないものなのか?

Updated: 2022年12月6日 — 01:45

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です