ページ

2022/05/08

ラズパイとラズピコで分電盤センサを作成②(SW最終形)

続いて、SWの最終形はこんなかんじ。

CT2DB.py(ラズパイ側常時起動プログラム)

import time
import datetime
import MySQLdb
from datetime import datetime as dt

import smtplib
import ssl
from email import encoders
from email import message
from email.mime import multipart
from email.mime import text

import serial

#ttyACM0はタイミングによって変わる可能性あり
ser = serial.Serial('/dev/ttyACM0', 115200)

filename = "ログファイル"

#メールを大量に送信しないようにするためのフラグ
sendmail = 0

from subprocess import getoutput

def _update():
    # ADCの電圧を取得する(USBより)
    str_serial = ""
    str_serial = ser.readline()
    while ser.in_waiting:
        str_serial = str_serial + ser.readline()

    #ラズピコからのテキストデータを整形する
    str_serial = str(str_serial).replace('b', '').replace('\\r\\n', '').replace('\'', '')
    nowdatastr = str(str_serial)
    str_list = str(str_serial).split(',')

    #データべースへの書き込み
    connector = MySQLdb.connect(host="localhost", db="log_current", user="root", passwd="パスワード", charset="utf8")
    cursor = connector.cursor()
    sql = u"insert into current values(now(), %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (str_list[0] , str_list[1] , str_list[2] , str_list[3] , str_list[4] , str_list[5] , str_list[6] , str_list[7] , str_list[8]>    #print(sql)
    try:
        cursor.execute(sql)
    except MySQLdb.Error as e:
        print(e)
        nowtime = dt.now()
        nowtimestr = nowtime.strftime('%Y/%m/%d %H:%M:%S')
        fileobj = open(file, 'a', encoding='UTF-8')
        fileobj.write(nowtimestr)
        fileobj.write(e)
        fileobj.close()
    connector.commit()
    cursor.close()

    #画面表示用の整形 0.4A以下の場合はノイズと区別がつかないので( )をつける
    for i in range(10):
        if float(str_list[i]) < 0.4:
            str_list[i] = "(" + str_list[i][:5] + "A)"
        else:
            str_list[i] = str_list[i][:5] + "A"
    str_list[16] = str_list[16][:4]

    #標準出力へ表示
    nowtime = dt.now()
    nowtimestr = nowtime.strftime('%Y/%m/%d %H:%M:%S')
    print(nowtimestr);
    print("CH0:" + str_list[0])
    print("CH1:" + str_list[1])
    print("CH2:" + str_list[2])
    print("Ch3:" + str_list[3])
    print("CH4:" + str_list[4])
    print("CH5:" + str_list[5])
    print("CH6:" + str_list[6])
    print("CH7:" + str_list[7])
    print("CH8:" + str_list[8])
    print("CH9:" + str_list[9])
    print("気温" + str_list[16] + "℃")
    print(" ")

    #ログファイルに現在時刻を書き込む プロセスの稼働監視用
    try:
        file = open(filename, 'w')
        lasttime = file.write(str(time.time()))
    except Exception as e:
        print(e)
    finally:
        file.close()

    #気温センサが70度以上を示した場合、メール送信
    if float(str_list[16]) > 70.0:
        sendAlertMail(1, str_list[16], '')

#メール送信用メソッド
def sendAlertMail(reason, temperature, errorbody):
    global sendmail

    #メール送信は条件を満たしたとき1回のみ行う
    if sendmail == 0:
        smtp_server_host = '送信メールサーバ'
        smtp_server_port = 送信メールサーバのポート番号

        account = 'アカウント名'
        password = 'パスワード'
        from_email = 'メールアドレス'

	msg = multipart.MIMEMultipart()
        msg["Subject"] = "ラズピコ:通報メール"
        msg["To"] = "メールアドレス"
        msg["From"] = "メールアドレス"
        if reason == 0:
            msgstr = "ラズピコが停止しました" + str(temperature) + "℃ " + errorbody
        elif reason == 1:
            msgstr = "高温です " + str(temperature) + "℃ " + errorbody
        elif reason == 999:
            msgstr = "正常です " + str(temperature) + "℃ " + errorbody
        else:
            msgstr = "不明な事象が起こっています " + str(temperature) + "℃ " + errorbody
        msg.attach(text.MIMEText(msgstr, 'plain', 'utf-8'))

        server = smtplib.SMTP(smtp_server_host, smtp_server_port)
        server.ehlo()
        server.starttls()
        server.ehlo()
        server.login(account, password)
        server.send_message(msg)
        server.close()
        sendmail = 1
        print("!!Send Mail!!")

def main():

    while True:
        _update()

if __name__ == '__main__':
    main()

CT_mesurement.py(ラズピコ内のプログラム)

※一部HTML表示が崩れるため、全角文字を使っている部分あり

import machine
import utime

from machine import Pin, SPI

#動作している間はオンボードLEDを点滅させる
led_onboard = machine.Pin(25, machine.Pin.OUT)

#ラズピコ内蔵の温度センサを利用
sensor_temp = machine.ADC(4)
conversation_factor = 3.3 / (65535)

#CSピンを使うことで、1つのSPIバスで2つのADCを制御
cs1 = machine.Pin(5, machine.Pin.OUT)
cs2 = machine.Pin(14, machine.Pin.OUT)
cs1.value(1)
cs2.value(1)

#SPIの初期化
spi = SPI(0, baudrate=300000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=Pin(2), mosi=Pin(3), miso=Pin(4))

ite1 = 0
ite2 = 0

Vref = 2.447 #TL431の基準電圧(テスタ測定値)
Resistance = [42.2, 41.8, 50.5, 41.9, 41.9, 42.1, 42.0, 41.9, 50.8, 43.2, 43.8, 50.2, 50, 50, 50, 50] #各chの抵抗値(テスタ測定値)
sampling_count = 10000 #RMSをとるサンプリング数
moving_avg = 3 #移動平均区間
CT_ratio = [1866.662, 1792.035, 2238.42, 1870.499, 1865.004, 1849.13, 1781.08, 1849.199, 2231.249, 1842.651, 2000, 2000, 2000, 2000, 2000, 2000] #chごとの計測結果の補正
bias_voltage_bit10 = [445.5, 445.3, 445.8, 445.5, 445.7, 445.0, 445.1, 444.9, 445.5, 446.9, 445.9, 446.0, 0, 0, 0, 0] #各chのバイアス電圧の初期値
prev_bias_voltage_bit10 = []

CHNs = [0, 1, 2, 3, 4, 5, 6, 7]

#10bit ADC MCP3008の場合
read_buf = bytearray(3)
write_buf_mcp3008 = []
for i in CHNs:
    write_buf_mcp3008.append(bytearray([0b00000001,
    	(0b00001000+CHNs[i]) << 4,
        0b00000000]))

for i in range(16):
    prev_bias_voltage_bit10.append([])
    for j in range(moving_avg):
        prev_bias_voltage_bit10[i].append(445)

adc_rawdata_sum = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
avg_voltage = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
avg_current = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

bit10_sum = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

while True: 
    for i in CHNs:
        #ch0~7に対する処理
        cs1.value(0)
        spi.write_readinto(write_buf_mcp3008[i], read_buf)
        cs1.value(1)
        bit10_0 = ((read_buf[1] アンド 0b00000011) << 8) + read_buf[2]
        adc_rawdata_sum[i] += ((((bit10_0 - bias_voltage_bit10[i])/1023)*Vref)**2) #ADCが読み取った値を瞬時電圧に変換し、2乗したものを加算
        bit10_sum[i] += bit10_0
        
        #ch8~15に対する処理
        cs2.value(0)
        spi.write_readinto(write_buf_mcp3008[i], read_buf)
        cs2.value(1)
        bit10_0 = ((read_buf[1] アンド 0b00000011) << 8) + read_buf[2]
        adc_rawdata_sum[i+8] += ((((bit10_0 - bias_voltage_bit10[i+8])/1023)*Vref)**2)
        bit10_sum[i+8] += bit10_0        
    
    ite1 = ite1 + 1
    
    if ite1 > sampling_count - 1:
        for i in CHNs:
            #ch0~7に対する処理
            avg_voltage[i] = (adc_rawdata_sum[i] / sampling_count) ** 0.5 #瞬時電圧の2乗の積算値から平均電圧を計算し、さらに平方根を計算(RMS)
            avg_current[i] = (avg_voltage[i] / Resistance[i]) * CT_ratio[i] #電圧RMSから電流RMSを計算
            adc_rawdata_sum[i] = 0
            #バイアス電圧の移動平均処理
            for j in range(1,moving_avg):
                prev_bias_voltage_bit10[i][j-1] = prev_bias_voltage_bit10[i][j]
            prev_bias_voltage_bit10[i][moving_avg-1] = bit10_sum[i] / sampling_count
            bias_voltage_bit10[i] = sum(prev_bias_voltage_bit10[i]) / len(prev_bias_voltage_bit10[i])
            bit10_sum[i] = 0
            
            #ch8~15に対する処理
            avg_voltage[i+8] = (adc_rawdata_sum[i+8] / sampling_count) ** 0.5
            avg_current[i+8] = (avg_voltage[i+8] / Resistance[i+8]) * CT_ratio[i+8]
            adc_rawdata_sum[i+8] = 0
            for j in range(1,moving_avg):
                prev_bias_voltage_bit10[i+8][j-1] = prev_bias_voltage_bit10[i+8][j]
            prev_bias_voltage_bit10[i+8][moving_avg-1] = bit10_sum[i+8] / sampling_count
            bias_voltage_bit10[i+8] = sum(prev_bias_voltage_bit10[i+8]) / len(prev_bias_voltage_bit10[i+8])
            bit10_sum[i+8] = 0

        print(avg_current) #ラズパイへ各chの電流値をシリアル送信
        ite2 = ite2 + 1
        ite1 = 0
        led_onboard.toggle()
        
        reading_temp = sensor_temp.read_u16() * conversation_factor #周囲温度を計算
        temperature = 27 - (reading_temp - 0.706) / 0.001721

spi.deinit()

2022/04/22

ラズパイとラズピコで分電盤センサを作成①(HW最終形)

ラズパイを使って家の電気使用量を調べたいーーー

ラズパイを使って何をするかを考えていた時に、割と初期からその想いはあったが、アイデアを実行に移すのは結構大変だった。半年ほどかけて勉強し、分電盤センサを作成したので、記録として残しておく。まずは出来上がった最終形を載せる。

やりたいこと:
家の分電盤に電流センサを仕込み、どの家電がいつどのくらい電気を使っているかを分かるようにする。主幹ブレーカーではなく、枝のブレーカー単位で測れるようにする。
→節電や、将来の電気プラン変更、太陽光発電の導入(?)時に参考情報として使いたい。

感想:
ブレッドボードでLチカ、とは違い、実際に使えるものを作ろうとするとこんなに苦労するのか、と思った。前半の勉強・実験パートもまあまあ大変だったが、後半の詳細設計・実装になると、モノ作りの経験がゼロのため、発想が出てこない。ネット上には要素実験、PoCをして終了、みたいな記事ばかりで、「で、実際作るときどうするの?」みたいな答えは少なかった。数少ないネット上の作例などを参考にしながらひたすら思考実験。部品を買うときも、実装するときも、失敗=即追加出費になるのも怖かった。こればかりは、経験を積まないとダメなんだろうなあ。

できあがった全体概要図はこのようなかんじ。


回路図はこう。

完成写真ものせる。
・電流センサ

・電流電圧変換基板

・ラズピコ基板

・分電盤施工後(汚い・・・)

使用したハードウェア
  • Raspberry Pi 3A+ ×1
  • Raspberry Pi Pico ×1
  • MCP3008 ×2
  • KCT-6 ×10
  • TL431シャントレギュレータ ×1
  • カーボン抵抗 多数
  • 積層セラミックコンデンサ 10μF 25V ×10
  • C型ユニバーサル基板 ×2
  • C型ユニバーサル基板用アクリル板 ×4
  • 樹脂ナット、スペーサ
  • 対熱ビニル電線 AWG22
  • UEW線
  • XHコネクタセット ×12
  • L型ピンソケット ×28
  • コネクタ付きケーブル
  • USBケーブル
  • アダプタ
ラズパイは除くとしても、トータル5000円以上かかっている。勉強とか実験で無駄な部品も買ったので、実際は10000円以上・・まあ、趣味と実益を兼ねていてかなり勉強にもなったので、安い方だろう。

ソフトウェア周りは次回。




2022/03/13

スクロールができなくなったトラックボールの分解(エレコム M-HT1URBK)

 エレコムのトラックボール M-HT1URBK を愛用していたが、ある日突然スクロールホイールが効かなくなってしまった。普段はホイールを回すたびにクリック感があるのだが、それもなくなり、完全に自由に回るようになってしまった。ドライバやユーティリティを更新してみたが、なおらないので、マウスを分解してみることにした。

【トラックボール分解】ELECOM HUGEの動画がとても参考になった。

このトラックボールはトルクスねじで止められている製品もあるようだが、自分のものは運よくプラスねじだった。

スクロールホイールまわりのみ分解を進める。



ホイール周りにゴミ等はみあたらない。


ここまできて原因判明。
ホイールの軸が折れている。。
軸が折れているので、ロータリーエンコーダに回転が伝わらないのが原因。
ロータリーエンコーダに六角レンチを入れて回してみると、ちゃんとクリック感があった。

折れた軸の部分を接着剤でつけようとしたのだが、軸が細すぎるのか素材と接着剤の相性が悪いのか、つけられなかった。スクロール以外は問題なかったので、しばらくつかってみたが、スクロールがないというのはとても不便だった。残念ながらこのトラックボールは処分行き。。

それにしても、スクロールホイールの軸はとても細い印象。こんなんでよくすぐ折れないな~。このホイールは2,3年はもった。

2022/02/27

Raspberry Pi Picoに触ってみる⑤

全開記事でラズピコの自動実行とその終了方法についていろいろ苦労したことを書いたが、その補足。 分電盤センサを作っている過程で、偶然見つけてた。

Thonny上でラズピコとラズパイのUSBシリアル通信を実行する方法。

基本は全開記事に書いてあるものと同じだが、うまくいかなかった理由は、「マウントポイント」の指定がうまくいっていなかったから。
いろいろなサイトにはUSBシリアル通信をするときは「/dev/ttyACM0」がデフォルト値として指定されているが、自分の環境ではttyACM1だった。

つまり、ラズパイ側でラズピコとシリアル通信を受信するプログラムの冒頭でマウントポイントを指示するわけだが、そこで正しく指示しないといけない。

マウントポイントの確認方法は、コマンドラインでdmesgコマンドを実行。

すると、接続デバイスに関するログがもろもろ出てくるので、その中から最新のマウントポイントと思しきも文字列を探す。

具体的なフローは以下。
ThonnyのMicroPythonでラズピコのシリアル送信プログラムを実行。

出力ウィンドウにはラズピコからの出力が表示される。

Thonnyのタブをラズパイの受信プログラムに切替、Pythonでプログラムを実行。

しかしここではttyACM0がビジーか見つからないかで怒られ、実行に失敗する。

この時点で出力ウィンドウからはラズピコの出力が見えなくなり、ラズピコを制御できなくなる。

コマンドラインでdmesgを実行。マウントポイントを確認。

ラズパイ側の受信プログラムのマウントポイントを変更、実行。

ラズピコからの受信データを受信プログラムが処理した結果が出力ウィンドウに表示される。


では停止はどうやるか。やはり、ThonnyのStopボタンからは停止できないため、ラズピコのRUNピンのショートが必要。ただ、前回と異なり、ラズピコ側をmain化していないので、最悪USBケーブルの抜き差しでも可能。ラズパイ側のSDカードが壊れるリスクは少ない。(と思いたい)

マウントポイントがわかったので、sudo unmountコマンドでラズピコプログラムを強制的に切れるかと思いきや、ダメだった。
ただ、main関数を使わずに実行できるようになっただけでも進歩である。


Raspberry Pi Picoに触ってみる④

自分の構想では、分電盤センサはラズピコとラズパイがクライアントサーバ型で役割分担している。ということは、ラズピコとラズパイが通信をする必要がある。

ラズベリーパイPicoでUSBシリアル出力する

を参考にラズパイとラズピコをシリアル通信するプログラムを作っていた。
しかし、うまくいっていない。

実行自体はうまくいく。定期的にラズピコの電圧を読みだしているように見える。
しかし、きちんとプロセスを終わらせることができない。
また、起動方法も独特で、
Thonnyのmicro pythonモードでラスピコプログラムを実行

タブを切り替えてpyhonモードにする

ラズパイ側のプログラムを起動
である。

Thonnyの出力ウィンドウにはPythonのラズパイプログラム出力か、MicroPythonのラズピコプログラムどちらかの出力しか表示されないので、分かりにくい。
Thonnyでラズピコのプログラムを起動しつつ、コマンドプロンプトでラズパイ側のプログラムを起動する手を考えたがうまくいかなかった。コマンドプロンプト側でUSBにアクセスできないと怒られる。

いろいろ試してみる。
全開記事のサイトをもとに、ラズピコ側のプログラムをmain.pyとして保存。
Thonnyを一回落とし、USBケーブルを抜き差し。
→LEDチカチカ。プログラムが自動実行された!

この状態で、ラズパイのコマンドプロンプトからラズパイ側受信プログラムを実行すると、コマンドプロンプトにラズピコから送られてきた電圧値が表示された!
やった!

喜びもつかの間。困ったことに。これらのプログラムの終了方法である。
上記のサイト通りにラズピコに接続しても

 Connecting to /dev/ttyACM0 (buffer-size 512)...

からつながらない。

Raspberry pi Pico 謎が解けた

のサイトどおりにやってもdevice is busyで怒られる。フラッシュボタンを押しても認識できない。うーん、どうしたものか。
ラズピコに書き込んだプログラムはバックアップを取っていないので、初期化することは避けたい。

いろいろなサイトを見て、書いてることを試して失敗した後、海外のBBSにたどり着いた。

Device is busy or does not respond - Raspberry Pi Pico

ここによると、バグかも。しかも修正版のリリースはまだ。BOOTSELボタンを押したまま起動し、ラズピコを再インストールするしかないのか。。。上記で作ったプログラムがすべて消えてしまう・・・

その後も粘って検索を続けたところ!
ラズピコのRUNピンとグラウンドをショートしてリセットを試したら、うまくできた!!

やり方はこう。

Thonnyを起動(MicroPythonモード)

RUNピンとGNDをショート

ラズピコが再起動

ThonnyのシェルでBackend terminated or disconnected. Use 'Stop/Restart' to restart.というメッセージが出る

そこでThonnyのSTOPボタンを押す

ラズピコの動作が止まる(main.pyに仕込んでいたLEDチカチカがとまる)

Thonnyのsaveウィンドウでラズピコのmain.pyを右クリックで削除

これでできた!



2022/02/26

Raspberry Pi Picoに触ってみる③

ラズピコとADCの組み合わせで遊んでいたら、面白い結果がでた。

ADCの各CHは、なにもピンを指さないとプログラム実行時初期値として40~500くらいのランダムな値をとる。

そこにグラウンド接続をしたピンを指してやると、7くらいで安定する。ところが、そこからまたピンを外すとかなりでたらめな値になる。3129を指したこともあった。これは誤差の範疇ではない。電子回路のグラウンド接続の重要性がわかるし、同時に計測していないときはどう接続しておけばよいのか迷う結果でもある。

Raspberry Pi Picoに触ってみる②

 次に、SPI通信でADC MCP3208のデータをラズピコで読んでみる。

Raspberry Pi Picoでプログラミング ⑬ spi APIとA-DコンバータMCP3008

このサイトを頼りに配線。



プログラムを移植するが、うまくいかない。SPIDEVが無いと怒られる。その他はC言語の解説サイトばかり。。今回自分が使っているのはMicroPython。

Raspberry Pi Picoを使ってADC(LTC2462)からカウント値を取得する

ここが参考になるかも。しかし、この通り書いても動かない。
SPIオブジェクトにそんなメンバはないと怒られる。。ではクラスのメンバは何があるのか?ということで

クラス SPI -- シリアルペリフェラルインタフェース バスプロトコル(マスタ側)

ここも参考にしながら格闘中。

今回はかなりドハマりしたが、以下結果。

  • ケーブル配線ミス。MCP3208のDoutを分圧してpicoに取り込んでいるが、電圧のより低い方に配線していた。
  • SPIの関数をどれにすればよいかわからなかった。総当たりで試した結果、結果的にはwrite_readintoがよかった。なぜこれが良くて他はだめなのか、技術的な観点は追いかけていない。
  • 配線設計ミス。CSピンをつないでいなかった。
  • CSピンをずっとグラウンドにつないでいる参考サイトがあって、そこを参考にしたのが一番の失敗→参考サイトの情報は信じてしまうので、こうなるとつらい。
  • CSピンはラズピコにつないで、きちんと1→0にしてやらないとデータを読んでくれない
  • ラズパイの時と違って、CSピンの制御はSPIの関数群に含まれていない。よって、自前での制御が必要
  • ラズピコにはSPIのID0と1がある。それはいいのだが、ID0の中でもピンが2セットある。どれを使えばいいのか、どれを使ってもいいのか、不明。これは実験してみてもよいかも。→後で試してみたら、4番ピン(SPI0 SCK)だとうまくいくが、9番ピン(SPI0 SCK)だとうまくいかなかった。これも技術的な理由は不明。

上記をうけて、回路図を書き直し。
うまくできた!







print文なしのシンプルなプログラムでADC読み込みをループした。
1秒当たり2564回実行していた。すると、1実行0.39msということになる。
毎ループ実行する必要がなさそうなものをfor文の外出しにしても0.3msくらい。

うーん、ラズパイ+ADCのときと変わらないか遅いくらい。。。。マイコンは余計な処理がない分、速いイメージがあったが、ラズピコにしてももそんなに早くならないんだな。プロセッサの違いだろうか。ボーレートを3倍にしても変わらず。

MCP3208のCH0~CH7を走査してみると、10000回実行に25秒かかった。すると、1実行2.5ms。
将来作りたい分電盤センサの要件と併せて考えてみる。
商用周波数50Hzをラズピコで計測するなら1波長8回サンプリング。ということは、サンプリング周期はπ/4。
交流電流のリアルな波形を捉えたいと思えば、ベストは電流の正弦波のピーク値をサンプリングできることだが、現状のラズピコだとワースト値はそこからπ/8ずれることになる。
正弦波であれば、そのずれは、cos(π/8)=0.999976 ん?意外と精度よいではないか。というか、この程度なら自宅使用で許容できる。


ラズパイとラズピコで分電盤センサを作成(14)

回路の実装図作成に取り掛かろうと思ったが、ここで一つ寄り道。 ADCへの入力前に交流電圧に対して直流バイアスをかける手段として、オペアンプを使う方法が出てきた。 アナデジ太郎の回路設計 というか、バイアスをかけたい場合、この方法の方がメジャー・・? と思ったのだが、オペアンプは以...