果報は寝て待て

2022年6月9日木曜日

pythonでラインの稼働状況を棒グラフに表示

 目的は、ちまたでよく見る、工場の自動ラインの稼働状況をモニタで表示することです。当然お金をかければなんぼでもあります。

今のところこんな風に表示されます。

 

 

 

やりたいことです。

① 1時間当たりの出来高を棒グラフで表示します。

② とりあえず48時間前まで表示して1時間ごとに更新します。

③ 棒の下には、月/日/時を表示します。

④真ん中の青い線は、出来高の目標線で、プログラムの立ち上げ時に入力します。

 

次に構成です。

① 自動ラインの最終工程から、完品が1台出るたびに2秒間の信号を出力します。

  三菱シーケンサーから、24vで取り出してます。

② 24vをフォトカプラで受けてaruduino unoで取り込みます。写真ではフォトカプラが6個並んでますが、今回は1個しか使ってません。



aruduino のプログラムです。


// data logger
//degital2~degital7 の6本のi/oを0.5秒毎にpythonへ送信する
//

int signal_1 = 2; // degital入力2 を signal_1に取り込む
int signal_2 = 3; // 
int signal_3 = 4; // 
int signal_4 = 5; // 
int signal_5 = 6; // 
int signal_6 = 7; // 



int led1  = 13;

int keep_sig1_on = 0;  // signal_1がonの状態を読み、ダブルカウント防止
int keep_sig2_on = 0;  //
int keep_sig3_on = 0;  //
int keep_sig4_on = 0;  //
int keep_sig5_on = 0;  //
int keep_sig6_on = 0;  //



void setup(){
    pinMode( signal_1, INPUT_PULLUP );
    pinMode( signal_2, INPUT_PULLUP );
    pinMode( signal_3, INPUT_PULLUP );
    pinMode( signal_4, INPUT_PULLUP );
    pinMode( signal_5, INPUT_PULLUP );
    pinMode( signal_6, INPUT_PULLUP );
    pinMode( 13, OUTPUT );
        
    Serial.begin( 9600 );

}

void loop(){
  //  signal_1がlowになったら keep_sig1_onを 1にする
    if(keep_sig1_on == 0){                    
      if(digitalRead(signal_1) == LOW ){
        keep_sig1_on = 1;
      }
    }
    else{
       if(digitalRead(signal_1) == HIGH ){  
         keep_sig1_on = 0;
        }
    }
  //  signal_2がlowになったら keep_sig2_onを 1にする
    if(keep_sig2_on == 0){                    
      if(digitalRead(signal_2) == LOW ){
        keep_sig2_on = 1;
      }
    }
    else{
       if(digitalRead(signal_2) == HIGH ){  
         keep_sig2_on = 0;
        }
    }
  //  signal_3がlowになったら keep_sig3_onを 1にする
    if(keep_sig3_on == 0){                    
      if(digitalRead(signal_3) == LOW ){
        keep_sig3_on = 1;
      }
    }
    else{
       if(digitalRead(signal_3) == HIGH ){  
         keep_sig3_on = 0;
        }
    }
  //  signal_4がlowになったら keep_sig4_onを 1にする
    if(keep_sig4_on == 0){                    
      if(digitalRead(signal_4) == LOW ){
        keep_sig4_on = 1;
      }
    }
    else{
       if(digitalRead(signal_4) == HIGH ){  
         keep_sig4_on = 0;
        }
    }
  //  signal_5がlowになったら keep_sig5_onを 1にする
    if(keep_sig5_on == 0){                    
      if(digitalRead(signal_5) == LOW ){
        keep_sig5_on = 1;
      }
    }
    else{
       if(digitalRead(signal_5) == HIGH ){  
         keep_sig5_on = 0;
        }
    }
  //  signal_6がlowになったら keep_sig6_onを 1にする
    if(keep_sig6_on == 0){                    
      if(digitalRead(signal_6) == LOW ){
        keep_sig6_on = 1;
      }
    }
    else{
       if(digitalRead(signal_6) == HIGH ){  
         keep_sig6_on = 0;
        }
    }
    

    
//  シリアルポートへ出力 bou_ok_count,ng_count,nc_count
    Serial.flush();
    
    Serial.print( keep_sig1_on );
    Serial.print( keep_sig2_on );
    Serial.print( keep_sig3_on );
    Serial.print( keep_sig4_on );
    Serial.print( keep_sig5_on );
    Serial.println( keep_sig6_on );
    

    

// 各トリガーが掛かれば、LED1を点燈する

    if(digitalRead(signal_1) == LOW){
      digitalWrite(led1,HIGH);
    }
    else if( digitalRead(signal_2) == LOW){
      digitalWrite(led1,HIGH);
    }
    else if( digitalRead(signal_3) == LOW ){
      digitalWrite(led1,HIGH);  
    }  
    else if ( digitalRead(signal_4) == LOW ){
      digitalWrite(led1,HIGH);
    }
    else if ( digitalRead(signal_5) == LOW){
      digitalWrite(led1,HIGH);
    }
    else{
      digitalWrite(led1,LOW);
    }
    
    delay(500 );
}

 

aruduinoは、ただ0.5秒ごとに 000000 をusbでpythonに送ってて、完品の信号が入力されたら 000001 を送信して、2秒たったらまた 000000 に戻すだけの仕事です。

③ LINUXMINT32BIT版に入ってるpython3 で、aruduinoからの信号を受け取り、1時間ごとに集計してリストに加え、tkinterで棒グラフ表示します。

 

# -*- coding: utf-8 -*-
"""
Created on Fri Apr 22 21:52:16 2022
7.1 for windows
@author: osada
"""

import tkinter as tk
import serial as sl
import time
import re
import datetime as dt

signal_1 = 0
signal_2 = 0
signal_3 = 0
signal_4 = 0
signal_5 = 0
signal_6 = 0

old_signal_1 = 1
old_signal_2 = 1
old_signal_3 = 1
old_signal_4 = 1
old_signal_5 = 1
old_signal_6 = 1

deki__count1 = 0
deki__count2 = 0
deki__count3 = 0
deki__count4 = 0
deki__count5 = 0
deki__count6 = 0
ave_774 = int(input("774Lの時間当たりの平均数を入力してください"))
data = []
#data= [10,15,20,15,10,14,12,10,5,7,15,16,9,14,12,11]
data_hour = []
#data_hour = ["04/27 21","04/27 20","04/27 19","04/27 18","04/27 17","04/27 16","04/27 15","04/27 14","04/27 13","04/27 12","04/27 11","04/27 10","04/27 09","04/27 08","04/27 07","04/27 06"]
now_time_hour = dt . datetime . now ( ).strftime("%m/%d %H")
data_hour.insert(0,now_time_hour) # data_hourリストの末尾に now_time_hour を追加する
triger_flug = 0
old_line = None
clear_count_triger = None
#ウインドを作成
root=tk.Tk()
root.geometry('1000x700')
root.title('canvasの使い方')

finFlag = True
serial = None

serial = sl.Serial('/dev/ttyACM0', 9600, timeout=0)
#serial = sl.Serial('COM5', 9600, timeout=0)  #

while( serial.is_open is False):
    time.sleep(100)

#図形を壁画するキャンバスをウインド上に作成
canvas = tk.Canvas(root, width = 1000, height = 650)
canvas.grid(column=1, row=3, columnspan=1,rowspan=8)
now_time_hour = dt . datetime . now ( ).strftime("%m/%d %H")
#stop_time=3600 #目標終了時間

#無限ループ
while True:
    #  シリアルポートの操作
    line = None
    if ( serial.is_open):
        line = serial.readline()
        new_line = line.strip().decode('utf-8')
        #print(new_line)
        
    if len(new_line) == 13 and old_line != new_line :
        
        now_time = dt . datetime . now ( ).strftime("%Y/%m/%d %H:%M:%S")
        #print(now_time)

        print("new_line ",new_line)
        #print("old_line ",old_line)
        signal_1 = re.search(r'A(.+)B',new_line).group(1)
        #print("signal_1 ",signal_1)
        signal_2 = re.search(r'B(.+)C',new_line).group(1)
        #print("signal_2 ",signal_2)
        signal_3 = re.search(r'C(.+)D',new_line).group(1)
        #print("signal_3 ",signal_3)
        signal_4 = re.search(r'D(.+)E',new_line).group(1)
        #print("signal_4 ",signal_4)
        signal_5 = re.search(r'E(.+)F',new_line).group(1)
        #print("signal_5 ",signal_5)
        signal_6 = re.search(r'F(.+)G',new_line).group(1)
        #print("signal_6 ",signal_6)
        print(now_time,"      ",signal_1,signal_2,signal_3,signal_4,signal_5,signal_6)
        old_line = new_line
        
    if signal_1 != old_signal_1 : # シグナル1に変化があった
        if signal_1 == "1" :
            deki__count1 = deki__count1 + 1 # 出来高カウントに1をプラス
            print("deki__count1  ",deki__count1)
        old_signal_1 = signal_1
        print(clear_count_triger,data)
    
    clear_count_triger = dt . datetime . now ( ).strftime("%M%S") #"%M%S" 毎時0分0秒がトリガー
    #print(clear_count_triger,data) # 0000
        
    if clear_count_triger == "0000" and triger_flug == 0 :
        now_time_hour = dt . datetime . now ( ).strftime("%m/%d %H")
        data.insert(0,deki__count1) # dataリストの末尾に deki__count1 を追加する
        data_hour.insert(0,now_time_hour) # data_hourリストの末尾に now_time_hour を追加する
        
        if len(data) > 48 : # dataリストが48要素を超えたら先頭を削除する
            data.pop()
        if len(data_hour) > 49 : # dataリストが48要素を超えたら先頭を削除する
            data_hour.pop()
        deki__count1 = 0 # deki__count1をクリアー
        triger_flug = 1
        now_time = dt . datetime . now ( ).strftime("%Y/%m/%d %H:%M:%S")
        print(now_time,data)        
        print(now_time,data_hour)
    if clear_count_triger != "0000" and triger_flug == 1:
        triger_flug = 0    
   
    canvas.delete("rect") # タグ"rect"を消去する
    canvas.create_text(500, 40, text= "774Lの過去48hの時間当たり生産数  " ,tag="rect",fill="black",font=('Helvetica 40 bold'))
    canvas.create_line(960-20*len(data), 600-(ave_774)*20, 1000 ,600-(ave_774)*20,tag="rect",fill="Blue",width = 2)
    for i in range(len(data)):
        canvas.create_rectangle(980-20*i,600-20*data[i],995-20*i,600,tag="rect",fill = 'green')
        canvas.create_text(988-20*i, 590-20*data[i], text= str(data[i]),tag="rect",fill="black",font=('Helvetica 8 bold'))
        canvas.create_text(988-20*i, 630, text= str(data_hour[i+1]),tag="rect",fill="black",font=('Helvetica 8 bold'), angle=90)
    root.update()
    

root.mainloop()

6ライン分の稼働状況を1台で管理できるよう6桁の文字列を送れるようにしてあります。

でもこれ以上拡張することはなさそうです。



2022年2月19日土曜日

winCAD3のデータをQCADで読み取る

数年前に退社された方のパソコンがなぜか私の手元に来ました。

 中をのぞくと、社内で10年以上にわたり作成、改造した冶具のcad図面のデータが入ってます。これは会社の貴重な財産です。パソコンごと捨てられる前にバックアップしました。 

 さて、それを利用したいのですが、win3.1の時代のWINCAD3という CADソフトのDXFデータです。迎え撃つのは、フリーのQCADです。

 結論として、今のところ、線の太さの情報がデータに入ってないようで、すべて同じ太さの線になりますが、破線や一点鎖線は元通りに取り込めます。コメント表示の文字化けもなくなりました。十分実用になります。

 

  やり方です。

 ① WINCAD3のデータをDXFの拡張子でPYTHONの実行ファイルと同じフォルダに保存する。

 DXFファイルの グループコードの値は次の事がわかってます。  

  0 種類(ARC,CIRCLE,TEXT,LINE等) 

  6 ラインタイプ(CONTINUOUS:連続線,HIDDEN:破線    1,DASHDOT:一点鎖線,)

  8 画層:レイヤー

  10 開始点のx座標

  20 開始点のy座標

  11  終了点のx座標

  21 終了点のy座標

  40 半径

  50 開始角度

  51 終了角度

  370 線の太さ ー1;画層による。-2;ブロックによる。 30:0.3㎜

 

②  テキスト部分をUNICODEに書き換える(PYTHONにやってもらいます)

    pythonと同じフォルダに置いてある"ABCD-00.DXF"を”ABCD-00X.DXF”に変換します。 

 'KANJI'と書いてある行を探し、その2行上の行のコードを変換します

  WINCAD3コードそのままで変換しないのは

             0x0000 ~ 0x0390  

             0x0450 ~ 0x2fff

             0x3001 ~ 0x3030

  それ以外は UTF-16に変換します。

また、'ff9f' は  '00b0'に変換します。

いまのところこれで文字化けには遭遇してません。

以下実行コードです

 

 

import glob
files = glob.glob("*.DXF")
for file in files:
print(file)

before2_line = ''
before1_line = ''
KANJI_flag = 0

with open(file) as f , open((str(file)[:-4])+"X.DXF",'w') as new_f :
l = f.readlines()
print(type(l))
print(l)
for line in l:
#print(line)
new_line = ''

if 'KANJI' in line :
#print(before2_line)
#print(before1_line)
for car in before2_line:
#print(car)
utf_car =hex(ord(car))
#print(utf_car)
utf_car2 = utf_car[2:]
#print(utf_car2)
"""
encode: ~ 0x3031
0x3000
0x044f ~ 0x0391
no_encode: 0x3030 ~ 0x3001
0x2fff ~ 0x0450
0x0390 ~ 0x0000
"""
if ((int(utf_car2,16) <= 0X3030 and int(utf_car2,16) > 0X3000)
or (int(utf_car2,16) <= 0X2fff and int(utf_car2,16) > 0X044f)
or int(utf_car2,16) < 0X0391 ) :
new_line = new_line +(car)
else :
if utf_car2 == 'ff9f' :
new_line = new_line +(r'\U+' + '00b0')
elif len(utf_car2) == 1 :
new_line = new_line +(r'\U+' + '000' + utf_car2)
elif len(utf_car2) == 2 :
new_line = new_line +(r'\U+' + '00' + utf_car2)
elif len(utf_car2) == 3 :
new_line = new_line +(r'\U+' + '0' + utf_car2)
else :
new_line = new_line +(r'\U+' + utf_car2)
#new_line = new_line[:-4]
print(new_line)
new_f.write(new_line )
KANJI_flag = 2
else :
if KANJI_flag == 0 :
print(before2_line)
new_f.write(before2_line )
#new_f.write(before2_line + '\n')

elif KANJI_flag == 2 :
KANJI_flag = 1
else :
KANJI_flag = 0

before2_line = before1_line
before1_line = line
print(before2_line)
new_f.write(before2_line )
print(before1_line)
new_f.write(before1_line )
print('EOF')
new_f.write('EOF' )
f.close()
new_f.close() 

>

 

 

③ QCADでファイルを開いて何もせずに上書き保存する。

    QCADで ○○X.DXFというファイルを一括で開いて、上書き保存→閉じる、上書き保存→閉じる、を全数繰り返します。これが一番面倒です。

  

④ テキストのSTANDARD 表示をUNICODEに変更する(PYTHONにやってもらいます)

      pythonと同じフォルダに置いてある"ABCD-00X.DXF"を”ABCD-00Y.DXF”に変換

 テキストごとに STANDARDというフォントを全ファイル一括でUNICODEに書き換えます。これでテキストの文字は日本語で表示されます。

 

import glob

files = glob.glob("*X.DXF")
for file in files:
print(file)

before2_line = ''
before1_line = ''
KANJI_flag = 0
with open(file) as f, open((str(file)[:-5])+"Y.DXF",'w')as new_f:
l = f.readlines()
print(type(l))
#print(l)
for line in l:
#print(line)
new_line = ''
if 'Standard' in line :
line = 'Unicode ' + '\n'
print(line)
new_f.write(line )
f.close()
new_f.close()      


⑤ 文字の大きさを変更する 

     pythonと同じフォルダに置いてある"ABCD-00Y.DXF"を”ABCD-00Z.DXF”に変換

   ”ABCD-00Y.DXF” で読めますが、文字サイズが大きく見ずらいので変更します。

文字の倍率は mag_textで指定します。0.7倍で見やすくなりました。


import glob
mag_text = 0.7
def is_num(x):
try:
float(x)
except ValueError:
return False
else:
return True


files = glob.glob("*Y.DXF")
for file in files:
print(file)
base_line = 0
before2_line = ''
before1_line = ''
line_count = 0
KANJI_flag = 0
line_list = []
with open(file) as f, open((str(file)[:-5])+"Z.DXF",'w')as new_f:
l = f.readlines()
print(type(l))
#print(l)
for line in l:
line_count = line_count + 1
line_list.append(line)
print (line_count)
#print(line)
#new_line = ''
for line_number in range (line_count):
if line_list[line_number] == 'AcDbText\n':
print (line_list[line_number + 8])
if is_num(line_list[line_number + 8]) :
line_list[line_number + 8] = str(float(line_list[line_number + 8]) * mag_text)+"\n"

for line in line_list :
new_f.write(line )
f.close()
new_f.close()      

実際の図面の絵のビフォーアフターを乗せれないのが残念です。

WINCAD3はネットでさがしてもほとんど情報が得られないので、古いデータの活用をあきらめてる方がおられるやも知れません。こんな再利用方法もありますよ。

2021年11月29日月曜日

pythonでのスレッド間通信

 またまたpythonねたです。次のような仕様のプログラムを作成中、スレッド間のデータの受け渡しが必要になりましたので、使い方の覚えとして記録しておきます。

1 webカメラで30フレーム/秒の画像を取り込み画面に表示する。

2 sirial通信でaruduinoのI/Oの値(オン、オフ)を取り込む(1回/秒)

3 それをトリガーに録画を開始、停止する。

4 累積された録画データが30分たまればハードディスクに落としていく。

 

画像処理は 1秒に30回まわります。aruduinoからの処理は1秒に1回回ります。ということでスレッド間通信が必要となりました。しかしオンオフだけでいいので今回はイベントを使います。

スレッド間のデータの受け渡しのサンプル、基本の使い方です。 

 

import threading
import time

def proc1():
    event.set()
    time.sleep(4)
    event.set()

def proc2():
    time.sleep(2)
    event.clear()
    time.sleep(5)

if __name__ == "__main__":
    event = threading.Event()
    th1 = threading.Thread(target=proc1)
    th2 = threading.Thread(target=proc2)
    th1.start()
    th2.start()

 これに1行ごとのevent内容をプリントさせたのが以下のプログラムです。

 

import threading

import time

def proc1():
    print("process1: start")
    print("proc1 event is ",event.is_set())
    event.set()
    print("proc1 event is set as ",event.is_set())

    
    time.sleep(4)
    print("proc1 event is ",event.is_set())
    event.set()
    print("proc1 event is set as ",event.is_set())

    print("process1: end")

def proc2():

    print("process2: start")

    time.sleep(2)
    print("proc2 event is ",event.is_set())
    event.clear()
    print("proc2 event is set as ",event.is_set())
 
    time.sleep(5)
    print("process2: end")

if __name__ == "__main__":
    event = threading.Event()
    th1 = threading.Thread(target=proc1)
    th2 = threading.Thread(target=proc2)
    th1.start()
    th2.start()


実行の結果

process1: start
process2: start
proc1 event is  False              # 最初は False です。        

proc1 event is set as  True      # proc1で True に変更されました
proc2 event is  True               # proc2で呼び出した時に True でした。
proc2 event is set as  False     # proc2で False に変更されました
proc1 event is  False              # proc1で再度呼び出した時に False でした。
proc1 event is set as  True     # proc1で True に変更されました
process1: end
process2: end

 

proc1とproc2の間でeventの値をきちんと引き継いでいます。このeventを録画スタート、ストップに使ってプログラムを作ります。

 

 


2021年10月17日日曜日

インタープランの無線モジュールを使ってみた。その2

 パトライトの無線化に気を良くして、次々と無線化していきます。

次は切削水のタンクです。ライン担当者はライン内の旋盤に切削水を補給する場合、遠いラインで50mくらい歩いてきて、集中タンクについているポンプスイッチを押します。1度スイッチを押すと5分くらいポンプが作動するように設定してます。その間にラインに戻ってコックを操作して切削水を補給します。

それをライン近くのリモコンのスイッチを押せば 、遠く離れた集中タンクのポンプが回りだすという、リモコンの見本のような使い方をします。リモコンは2台配置します。

上がリモコンのケースです。前回はアルミケースを使用したので尻尾をケースから出す必要がありました。今回は収納して大丈夫と思います。
上の左がリモコン側、右が受信側です。

左がリモコン側、右が受信機で集中タンクに取り付けます

簡単に無線化できる。またその時の状況に応じて増やせるというのはほんと、便利ですね。


2021年10月15日金曜日

旋盤のチャックの爪の芯ズレを調べる方法

 旋盤で、ワークを加工する時、爪で掴みます。爪は金属製、鬼爪(インサート)と言って繰り返しの使用時に摩耗しにくい材料がワークに直接当たるように作ってあります。

旋盤をぐるぐると回したときに中心とワークを掴む爪の中心が一致していればよいのですが、そのためには爪を取り付けするたびに削らないといけません。まして鬼爪を装着してる爪は削れません。

ではどうするのかと言いますと、旋盤に常時固定の親爪と、鬼爪が装着されてる子爪との間にシム(薄っぺらい金属片)をはさんで爪の中心をずらして、旋盤の軸芯(中心)に近づけてやります。

今回、どの爪にどれだけのシムをかますべきなのかの計算方法を説明します。

1, 芯出しリングを使用する方法

爪1、爪2、爪3の三本の爪が旋盤についていてこれらの中心にワークを置いて、爪はそれぞれ中心向きの力でワークを支えます。(外径クランプ)


 

上図のように芯出しリングをクランプさせます。芯出しリングとは、外径の中心と内径の中心が一致している真円状のリングです。厳密に言えば存在しませんが、ほぼそんなイメージです。


芯出しリングの内径にピックインジケータやダイヤルゲージをあてて、主軸(チャック)をぐるりんと手で回します。

その時、旋盤の軸芯と爪の中心が一致していればダイヤルゲージの振れは0のままです。

では上図のように爪の中心が旋盤の軸芯から距離aだけ爪1の方向へずれていたらどうでしょうか?

その場合ダイヤルゲージの最も遠い位置が爪1の方向になり、ズレの距離は

a = (最も近い位置のゲージ読み -最も近い位置のゲージ読み)÷2

となります。例えば最も近い位置のゲージ読みが +0.3mmで遠い方が-0.1mmだと a=0.2mmとなります。ここまではイメージしやすいと思います。

 

2、 芯出しリングを使わない方法

 芯出しリングの使用はある程度正確にでるのですが、使えない場合が結構あります。

 1 適切なサイズのリングを持ち合わせていない

 2 あるけど、爪の跡やさびでがたがた

 3 ロケーション(リングを旋盤方向へ押し当てるための台)が爪より外にある

こういう時の方法の一つにワークの内径を切削させる方法があります。

 1 4つ爪使用時の方法

   比較的簡単です。下図のようなワークとします。


下図のようにクランプします。
内径を切削した所下図のようになりました。

距離bだけ爪1のほうへワークが寄っているのです。
この状態で(ワークを一度もアンクランプしない)ダイヤルゲージを当てて主軸を手で回すと振れは理論上0ですね。


次にワークをアンクランプし、180度回転させて再度クランプします。黒の点は旋盤の軸芯、緑の点は爪の中心、赤い点はワークの内径の中心です。

上図のようにダイヤルゲージを当てて回すと、最小値(最も遠い所)の方向に爪の中心が寄っていることがわかります。また寄っている距離bは

b = (min - max)÷4

例えばダイヤルゲージの最大値が0.5mmで最小値がー0.1mmだとすると  b = (0.5 + 0.1)/4 = 0.15 となり、爪の中心は爪1の方向へ0.15mm寄っていることがわかります。

その場合爪1側に0.15x2 = 0.3mmのシムをかませます。 なぜ2倍の厚みのシムなのかは別の機会があれば説明します。

ここまではイメージしやすいと思います。

  

  2  3つ爪使用時の方法

ここからが今回の本番です。下図のようなワークと爪があります。


じ実際に内径を切削すると下図のようになりました。
爪の中心は距離cだけ爪1の方向へ寄っています。このままダイヤルゲージは0のまま触れません。180度回転させることはできません。

そこで、ワークをアンクランプさせ、時計方向に120度回転させて再度クランプさせますと下図になります。


拡大してみると下図になる。

Oc : 爪の中心(チャックの中心)

Os :  旋盤の軸芯(中心)

On :  ワークの内径の中心

Onは、回転させる前はOsと同じ場所でしたが、Ocを中心に120度時計方向に移動しました。


ダイヤルゲージをあて主軸を手でまわすと(Osを中心に回転)、最大値と最小値の差は 2 x (On,Os間の距離)となります。三角形(On,Oc,Os)は120度の角度を持つ2等辺三角形になるので、cを求めると

c = (最大値と最小値の差)÷2÷√3

となります。また最も遠い点は、点Onと点Osを通る線になるので、爪1との方向のズレは ー30度となります。

 

   最後に3つ爪方法をまとめると、

材料をクランプさせ内径切削する。

アンクランプさせ、120度時計方向にかいてんさせ、再度クランプさせる

③ダイヤルゲージで振れの最大値と最小値の差から距離cを求める

       c = (最大値と最小値の差)÷2÷√3

④最小値(最も遠い点) から時計方向に30度回転させた方向に、距離cだけ爪の中心はずれている

例えばダイヤルゲージの読みの最大値が 0.1で、最小値が ー0.4だとすると c = (0.1+0.4)/2/√3 =  0.14  がズレの距離となります。


理論通りにはなかなかいきませんが目安としての使用には有効で10年以上使ってます。




2021年10月2日土曜日

インタープランの無線モジュールを使ってみた



職場(工場)の改善でインタープランの無線モジュールを使う機会がありました。


多種類ある無線モジュールの中でも、本体のみで接点モードが使えるということでこれを選びました。 

導入の背景です。

作業されてる方は設備1と設備2の複数のラインを担当しています。その方が設備2の検査場所にいると、設備1の停止の音をしらせるパトライト1は設備の向こう側にあります。

 

そのため設備停止がすぐにわからず、停止時間が長引くという不具合が起こってます。

 そこで次のように変更します。 


パトライト1の信号を有線でIM920sLへ送り、無線で親機に飛ばし、有線でブザーユニットにつないで鳴らします。

これで作業されてる方が設備2の検査場所にいても設備1の停止がすぐにわかります




 

 

無線モジュールの設定です。

 ① 親機の設定

  1.  REG SW を押しながら電源を入れます。

    2.  STATUS LED 2 回点灯した時点でREG SW を離します。

     この時点で早い点滅を繰り返してる場合は、何度か 1. をやり直します。

  3. REG SW を再度押し、LED が連続点滅になるまで押し続けます。(約3 秒)

  4. REG SW を再度押し、LED 0.5 秒に1 回の点滅となるまで(約3 秒間)押し続けます。

    電源を切らずにこのままの状態で子機のそばに置いときます。

② 子機の設定

  1.  REG SW を押しながら電源を入れます。

    2.  STATUS LED 2 回点灯した時点でREG SW を離します。

     この時点で早い点滅を繰り返してる場合は、何度か 1. をやり直します。

    STATUS LED は連続点灯に変化すると設定完了です。

 

将来的に送信機は増える可能性があるので、子機に。受信機を親機にします。

2.5㎜ピッチの変換アダプタも購入してユニバーサル基盤に組みます。

子機(送信機)側です。

IM920c用変換アダプタ(IM920c-ADP)のピン配置がどこにも載ってなかったみたいですが、テスターで当たって上の図のようになってることがわかりました。

パトライトの信号線(LOWにしたときにオンします。  )をフォトカプラで電圧変換してIM920sLのP1に入れます。

P9がハイ、P10がローで 接点モードの送信機となります。

次に親機(受信機)側です。


 3端子レギュレータは発熱を考え、ロームのDC/DCコンバータを使いました。

有効性が確認できれば他のパトライトの信号も受信させる予定です。受信機の場所も簡単に変更できます。

中華製WIFIのモジュールがワンコインで買える時代、インタープランの無線モジュールは1台5000円く近くしますが、日本製の無線モジュールという事でとても安心感がありますね。遊びで使うのでなく、仕事でならなおさらです。

 


2021年5月10日月曜日

工場のライン内の監視カメラを作ってみた

 ライン内で加工不良が出ます。

 不良の出る原因がわかればその原因を取り除けば対策完了です。

しかし、原因がわからない場合があります。今回はその中でも、怪しい箇所が4箇所あるラインに監視カメラを つけて調査しました。


 ①カメ;ワークストッカーから搬送用ロボットがワークを取り出す様子を写します

②カメ;取り出した時の角度の、マスターとの微細なズレを測定します。その様子を写します。

③カメ;微細な角度のズレを補正して、旋盤1へ取り付ける様子を写します。

④カメ;微細な角度のズレを補正して、旋盤2へ取り付ける様子を写します。

 

監視カメラの条件

 1、 長時間録画できること (上書き保存できて最新5時間が常に録画)

 2、 4台のカメラで監視できる

 3、 1画面を4分割して録画できる(時間のズレ防止のため)

 4、 時刻もどっかに録画できる

 5、 イベントスイッチを押したら、その以前の録画は上書きされずに残っている

 6、 安い事

カメラはロジクール。これを4台使います。コロナ以前は1000円以下で購入できたのですが今は2500円です。在宅勤務で需要が多いから?

ビデオソフトですが、かゆいところに手が届くソフトはないのでpythonで作ります。コードは最後に添付します。

録画中、以下のような画面になります。 

1秒間に30フレームの仕様ですが実際は20フレームくらいにコマ落ちしてますので再生時は1.5倍速くらいのスピードで再生されます。パソコンの性能によるものと思います。また20分(1200フレーム)で1.2Gbくらいのサイズになります。

画面左上に現在時刻が表示されます。左上がワークをロボットで取り出す所、右上が取り出したワークの位相(角度)を調べる所、左下が旋盤1にワークを取り付ける所 、右下が旋盤2にワークを取り付ける所を録画してます。

 pythonを利用したら1万円くらいで、4カメの監視装置が簡単に構築できる時代です。 

 私の拙いpythonプログラムでも実用になりました。工場での改善活動に挑戦されてる方、pythonにも挑戦してみませんか?

 

 

 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar  4 21:32:24 2021
監視カメラ 4台用
 約20分ごとにビデオをmp4で記録していく
 12ファイルたまれば古いファイルから削除していく
 小文字の z を長押しすれば「event hh:mm:ss 」と表示し、その時点で溜まっているファイルは削除しない
 小文字の q を押せば終了する。
@author: toru
"""

import cv2
import datetime
import os
import time

d = datetime . datetime . now ( )
time_stump = str(d . hour)+":"+str(d . minute)+":"+str(d . second)
print(time_stump)

"""
VideoCapture(*) は0〜4を指定する。
どのカメラが何番になるのかはusbポートに差し込む順番にもより、不明
合成画面を見て変更すること 2134
"""
camera1 = cv2.VideoCapture(2)                
camera2 = cv2.VideoCapture(1)                
camera3 = cv2.VideoCapture(3)                
camera4 = cv2.VideoCapture(4)


# 動画ファイル保存用の設定
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')        # 動画保存時のfourcc設定(mp4用)
fps1 = int(camera1.get(cv2.CAP_PROP_FPS))                    # カメラ1のFPSを取得
w1 = int(camera1.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ1の横幅を取得
h1 = int(camera1.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ1の縦幅を取得
print("camera1",fps1, w1, h1)


fps2 = int(camera2.get(cv2.CAP_PROP_FPS))                    # カメラ2のFPSを取得
w2 = int(camera2.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ2の横幅を取得
h2 = int(camera2.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ2の縦幅を取得
print("camera2",fps2, w2, h2)


fps3 = int(camera3.get(cv2.CAP_PROP_FPS))                    # カメラ3のFPSを取得
w3 = int(camera3.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ3の横幅を取得
h3 = int(camera3.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ3の縦幅を取得
print("camera3",fps3, w3, h3)

fps4 = int(camera4.get(cv2.CAP_PROP_FPS))                    # カメラ4のFPSを取得
w4 = int(camera4.get(cv2.CAP_PROP_FRAME_WIDTH))              # カメラ4の横幅を取得
h4 = int(camera4.get(cv2.CAP_PROP_FRAME_HEIGHT))             # カメラ4の縦幅を取得
print("camera4",fps4, w4, h4)



video_count = 1
video_list = [1]
video_mix = cv2.VideoWriter('video_mix'+str(video_count)+'.mp4', fourcc, fps1, (w1+w2, h1+h2))  # 動画の仕様(ファイル名、fourcc, FPS, サイズ)
print("camera_mix",fps1, w1+w2, h1+h2)  # フレーム数、サイズを合わせないとエラーが出る
start_time = time.time()
print("start",start_time)             # スタート時間             

# 撮影=ループ中にフレームを1枚ずつ取得(qキーで撮影終了)
while True:
    ret, frame1 = camera1.read()              # カメラ1のフレームを取得
#       time_stump in frame1
    d = datetime . datetime . now ( )         #現在時刻を取得
    time_stump = str(d . hour)+":"+str(d . minute)+":"+str(d . second)    #時刻をタイムスタンプに変更
    cv2.putText(frame1, time_stump, (0, 50), cv2.FONT_HERSHEY_PLAIN, 3, (0, 0, 255), 3, cv2.LINE_AA)      #タイムスタンプをフレーム1に書き込み

    ret, frame2 = camera2.read()              # カメラ2のフレームを取得
    ret, frame3 = camera3.read()              # カメラ3のフレームを取得
    ret, frame4 = camera4.read()              # カメラ4のフレームを取得


    frame_hmix1 = cv2.hconcat([frame1, frame2])  # conect horizonal frame1とframe2を水平方向に結合しframe_hmix1とする
    frame_hmix2 = cv2.hconcat([frame3, frame4])  # conect horizonal frame3とframe4を水平方向に結合しframe_hmix2とする

    frame_mix = cv2.vconcat([frame_hmix1, frame_hmix2])  # conect vurtical frame_hmix1とframe_hmix2を垂直方向に結合しframe_mixとする
    cv2.namedWindow('camera_mix', cv2.WINDOW_NORMAL)
    cv2.imshow('camera_mix', frame_mix)       # frame_mixを画面に表示
    video_mix.write(frame_mix)                # frame_mixをvideo_mixに録画する
    now_time = time.time()
    #    print("now_time",now_time)

    """
    20分(1200)経ったら video_mixを終了し、1繰り上げた番号をつけて保存する
    """
    if now_time - start_time > 1200 : #20min = 1200
        start_time = now_time
        video_mix.release()
        video_count = video_count + 1
        video_mix = cv2.VideoWriter('video_mix'+str(video_count)+'.mp4', fourcc, fps1, (w1+w2, h1+h2))  # 動画の仕様(ファイル名、fourcc, FPS, サイズ)
        
        video_list.append(video_count)    #  video_listに video_countを追加する
        if len(video_list) > 12 : #ファイルが指定数(12)を超えたら、削除する
            path = 'video_mix'+str(video_list.pop(0))+'.mp4'
            if os.path.exists(path) :
                os.remove(path)

    # 'q'を押したらwhileループを抜ける
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    # 'z'を押したら「event」と時間をプリントして削除リストを空にする
    if cv2.waitKey(1) & 0xFF == ord('z'):
        print("event",time_stump)
        video_mix.release()
        start_time = now_time
        video_count = video_count + 1
        video_mix = cv2.VideoWriter('video_mix'+str(video_count)+'.mp4', fourcc, fps1, (w1+w1, h1+h1))  # 動画の仕様(ファイル名、fourcc, FPS, サイズ)
        video_list = []
        video_list.append(video_count)
        


# 撮影用オブジェクトとウィンドウの解放
camera1.release()
camera2.release()
camera3.release()
camera4.release()

video_mix.release()
cv2.destroyAllWindows()

"""
"""