2009年2月17日火曜日

ctypes(その2) MBT.dll を Python から

問題は、ポインタやハンドラ渡しの変数をどう扱うかです。

いろいろ試してみて、なんとかこれでいいのではないか?と思われる方法です。MBT.dll の公開されている API (Google検索)を見てください 。
  1. ctypes のインポート
  2. PLC との通信に必要な変数の設定
  3. ハンドラ、ポインタの型 を c_long() に  < この辺が大事みたいです。
  4. def connect() 関数 < socket が ハンドラ です
  5. def disconnect()関数
  6. def read()関数 < ReadData が ポインタ です
  7. def write()関数 < WriteData が ポインタ です
  8. 各関数を順番に実行します
  9. ## は VBA で使う時のサンプル

  • 変数をあらかじめ c_long() 等宣言する
  • 関数では byref()で渡す
  • 中身は、 ***.value で扱う
ソフトウェア用語を使ったうまい説明はできません、用語を知りませんので。
 <<16、>>16 は 16 bit のデータを Python 32 bit、 で受けた(?)ので、ずらしてます。
SwapDWord() は、インテル系 CPU の変なバイトオーダーに合わせる為の関数です。 

16 bit の 整数型、だけしかうまく動いてません。また時間があったらがんばります。


# $Id$
# Win32
# Python 2.5 (ctypes)
# MBT.dll by Wago (Modbus over TCP)
# copy MBT.dll to c:\windws\system32
# SYSTEM 750-841 Wago PLC
#
# data = int (16 bit) only
#

from ctypes import *

PLCAddress = '192.168.1.2' # PLC adress or name
Port = 502 # Modbus TCP port, need to be enabled
BaseAddress = 0x3000 # PLC, IEC 61131-3 Addresses %MW0..
length = 1 # data length

socket = c_long() # handler
ReadData = c_long() # pointer
WriteData = c_long() # pointer

def connect():
state_a = windll.MBT.MBTInit()
state_b = windll.MBT.MBTConnect(PLCAddress, Port, 1, 3000, byref(socket))

print ('PLC address = ',PLCAddress)
print ('Socket = ',socket.value)
print ('MBT Init state = ',state_a)
print ('MBT Connect state = ',state_b)


def disconnect():
state_a = windll.MBT.MBTDisconnect(socket)
state_b = windll.MBT.MBTExit()

print ('MBT Disconnect state = ',state_a)
print ('MBT Exit state = ',state_b)


def read(OffsetAddress, c=None):
ReadAddress = BaseAddress + OffsetAddress
state_a = windll.MBT.MBTReadRegisters(socket,4,ReadAddress,length,byref(ReadData),0,0)
if state_a != 0 :
print (' **** PLC read error')
c = windll.MBT.MBTSwapDWord(ReadData.value) >> 16

print ('MBT Read resisters state = ', state_a )
print ('Read address = ', ReadAddress )
print ('Read data = ',c )

return c



def write(OffsetAddress, data, c=None):
WriteAddress = BaseAddress + OffsetAddress
WriteData.value = windll.MBT.MBTSwapDWord(data << 16)
state_a = windll.MBT.MBTWriteRegisters(socket,WriteAddress,length,byref(WriteData),0,0)
if state_a != 0 :
print (' **** PLC write error')

print ('MBT Write resisters state = ', state_a )
print ('Write add = ', WriteAddress )
print ('Write data = ', WriteData.value )

## read back
c = read(OffsetAddress)
return c


connect()
write(0,1)
data = read(0)
print(data)
disconnect()





## -----------------------------------------------
## 'VB source
##Public Const MODBUSTCP_TABLE_OUTPUT_REGISTER = 4
##Public Const MODBUSTCP_TABLE_INPUT_REGISTER = 3
##Public Const MODBUSTCP_TABLE_OUTPUT_COIL = 0
##Public Const MODBUSTCP_TABLE_INPUT_COIL = 1
##Public Const MODBUSTCP_TABLE_EXCEPTION_STATUS = 7
##
##Public Declare Function MBTInit Lib "MBT" () As Long
##Public Declare Function MBTExit Lib "MBT" () As Long
##Public Declare Function MBTConnect Lib "MBT" (ByVal szHostAddress As String, ByVal port As Integer, ByVal useTCPorUDP As Long, ByVal requestTimeout As Long, hSocket As Long) As Long
##Public Declare Function MBTDisconnect Lib "MBT" (ByVal hSocket As Long) As Long
##Public Declare Function MBTReadRegisters Lib "MBT" (ByVal hSocket As Long, ByVal tableType As Byte, ByVal dataStartAddress As Integer, ByVal numWords As Integer, pReadBuffer As Any, ByVal fpReadCompletedCallback As Long, ByVal callbackContext As Long) As Long
##Public Declare Function MBTReadCoils Lib "MBT" (ByVal hSocket As Long, ByVal tableType As Byte, ByVal dataStartAddress As Integer, ByVal numBits As Integer, pReadBuffer As Any, ByVal fpReadCompletedCallback As Long, ByVal callbackContext As Long) As Long
##Public Declare Function MBTReadExceptionStatus Lib "MBT" (ByVal hSocket As Long, pExceptionStatus As Byte, ByVal fpReadCompletedCallback As Long, ByVal callbackContext As Long) As Long
##Public Declare Function MBTWriteRegisters Lib "MBT" (ByVal hSocket As Long, ByVal dataStartAddress As Integer, ByVal numWords As Integer, pWriteBuffer As Any, ByVal fpWriteCompletedCallback As Long, ByVal callbackContext As Long) As Long
##Public Declare Function MBTWriteCoils Lib "MBT" (ByVal hSocket As Long, ByVal dataStartAddress As Integer, ByVal numBits As Integer, pWriteBuffer As Any, ByVal fpWriteCompletedCallback As Long, ByVal callbackContext As Long) As Long
##Public Declare Function MBTSwapWord Lib "MBT" (ByVal wData As Integer) As Integer
##Public Declare Function MBTSwapDWord Lib "MBT" (ByVal dwData As Long) As Long
##
##Private Socket As Long






2009年2月16日月曜日

Wago PLC System 750

ctypes(その2)の記事(code)の準備。近いうちに ctypes を使ったもう少しややこしい .dll の使い方の例を書きます。その .dll は、 MBT.dll という Wago の PLC のインターフェース用の .dll です。その前に ワゴ の PLC がどういうものなのか説明します。Wago PLC および、その開発環境CodeSys(通信用の MBT.dllも)は、残念ですがフリーではありません。

ネットワークの先に、I/O 機器を接続して、Python からそのデータを読み書き出来ると便利です。方法はいろいろありますが、その I/O 機器をマイコン+ネットワーク等と始めるとシステムが出来るのが何ヶ月先になるか分かりません。また市場で安く、信頼性のあるものを買って済ませることができれば、ありがたいです。Wago のリモート I/O でそういった使い方ができました。産業用の Modbus というプロトコルを MBT.dll 経由で TCP/IP に乗せて使います。

問題は、ハードは比較的安いのですが、開発環境が高価です、残念です。ターゲット側の開発にも、それなりのスキル(ST言語等)が必要です。が、ネットワーク I/O システム全体を(マイコン+TCP/IP 等で)発注して、アセンブリ言語ベースのマイコン ネットワークシステム をフルスクラッチするよりは、ある程度の標準化(分割発注も)ができるので、資産の使い捨てにならないのが救いです。何より、これで監視・制御ソフトは Python で自分で作れます。

ネット I/O の親(CPU)は、カプラと呼ばれます、ネット無しでも制御プログラムや多少面倒な変換ぐらい組み込めます。それどころか本来、CPU単品で1mSec 毎の制御をすべて自前でこなせるぐらいの能力があります。このカプラに必要に応じて、I/O のモジュールを、レゴ ブロックの感覚で拡張します。

Wago 750 シリーズ のカプラ(CPU)は、ARMナンチャラ、OSは、リアルタイム組み込み Linux、これだけで何が出来るか大体想像できると思います。Web server、Mail、FTP、TCP/IPやりたい放題です。旧来のお客様のために、TCP/IP、UDP に加え Modbus I/F もあります、というか、こちらがメインです。

そこで、前出のMBT.dll Modbus over TCP/IP (.dll)の登場です。Modbus プロトコル を TCP/IP に乗せて、PCとやりとりします。Python にそういうモジュールがあればいいのですが、あるのかもしれませんが私にとって手軽ではないので、PC と Wago PLC 間の通信に、MBT.DLL を使います。


システム構築に必要な物
  1. 産業用、DC24V電源
  2. PLC: Wago system 750-841 等 Ethernet カプラ(親)
  3. I/O module:Wago system 750- シリーズ AI、AO、DI、DO 等 
  4. ネットワーク用 ケーブル、スイッチ等
  5. WindowsPC (+Python + NIC)
  6. PLC 開発用ソフトウェア:WAGO-IO-PRO32、WAGO-IO-PRO CAA等(Codesys)(有償)
  7. MBT.DLL (有償)
分かりやすく?するために昔の VxWorks system との雑な対応表を書きます、と

PC:PC:PC
Development Env:Tornado:CodeSys
DevLang:c:ST or ladder
BSP:BSP(I do not know):sometarget.exe(forgot!) and MBT.dll
Target Master:MVME-xxx:Wago system750-xxx
Target Bus:VMEBackPlane:(Include with Wago system)
Target I/O:VME I/O Boards: Wago I/O modules
Target OS:VxWorks:some embedded Linux 



2009年2月14日土曜日

CSVファイルの読み書き(2)

標準CSVモジュール を使った、CSV ファイルの読み書き(その2)。 以前の記事を書いた時は、標準の CSV モジュールの使い方が、よく理解できていませんでした。今回は、標準の CSV モジュールを使った ファイル IOです。 

CSV ファイルの数値データを list にします。以降のプログラムで
 List[0][0]
といった形でデータを呼び、処理が出来るようにします。
  1. CSV モジュールのインポート
  2. 文字列データを数値(float)に変換する関数を準備。リスト、サンプルデータの初期化。
  3. サンプルデータを、sample.csv というファイルに書き込みます。FW0。
  4. sample.csv を開きます。FR0。文字列データを datastr のリストに、数値化したデータを datanum のリストに保存します。
  5. 読み出したデータのチェック。
  6. 変数 row に残っている文字列のリスト を、1 行づつ 4 回書き込みます。FW1。
  7. datastr の文字列のリスト を ファイルに書き込みます。FW2。
  8. datanum の数値のリスト をファイルに書き込みます。FW3。
2.のところで
  def s2f(x): return float(x)
と関数を定義しているのは、
4.で文字列のリストを数値化する
datanum.append(map(s2f,row))
map()
を利用するには、処理が関数でないといけないからです。





## $Id$
## win32
## Python2.5
## csv file io (for numeric data)

import csv

def s2f(x): return float(x)
datastr = []
datanum = []
datasample = [[1.0,2.0],[3.0,4.0]]

fw0 = open('sample.csv','wb')
writer0 = csv.writer(fw0)
writer0.writerows(datasample)
fw0.close()

fr0 = open('sample.csv', 'rb')
reader0 = csv.reader(fr0)
for row in reader0:
datastr.append(row)
datanum.append(map(s2f,row))
fr0.close()

print (reader0.line_num)
r = 1
c = 1
print ('string data [',r,'][',c,'] = ',datastr[r][c])
print ('numeric data [',r,'][',c,'] = ',datanum[r][c])

print(row) # data for row write test, form last row
fw1 = open('csvout1.csv','wb')
writer1 = csv.writer(fw1)
writer1.writerow(row)
writer1.writerow(row)
writer1.writerow(row)
writer1.writerow(row)
fw1.close()

fw2 = open('csvout2.csv','wb')
writer2 = csv.writer(fw2)
writer2.writerows(datanum)
fw2.close()

fw3 = open('csvout3.csv','wb')
writer3 = csv.writer(fw3)
writer3.writerows(datastr)
fw3.close()



2009年1月7日水曜日

Py3K にとりあえず対応

深刻なエラーを回避します。当面エラー無しで動くようにしないと話になりませんので。
print と raw_input() が変わっているようですので、先ずそこだけでも、の対策です。

print
print を print() に変更します。
カッコで囲みます。
変更後も 2.5 でも動きます。

raw_input()
raw_input() を input() に変更します。
変更後は、2.x では動かなくなります。

2008年12月12日金曜日

Python 3K

あぁ、早く 2.6 に上げないと、と思っていたら。 3K のリリース。
いまのところ print () にするのと、多用してる raw_input() をなんとかすればよさそうなので、運用中の PC は、2.6 に上げて。開発用の自分の PC は 3K にしました。

まぁ、積み上げている資産も、そんなにありませんので、使うごとに確認しながらメンテしようと思っています。

GUI の方針を変更しました。Qt はやめにして、WxPython にします。
Wx にも簡単 GUI ビルダ が見つかったので。資料の豊かさ。メインストリームから離れすぎない、(いや、Python て時点で既に。。)ように Wx で進めます。

あと、Python 2.6 でも 3K 対応のコードの実行も大丈夫にするようなモジュール等が、あると思いますので探します。

2008年10月23日木曜日

ここの概要

対象読者
  • Python の「変な使い方」に興味がある方
  • PC による制御・計測・データ解析に興味がある方
  • PCのプログラムを「現実」の世界につなげたい方
  • 何もかも安く済ませたい方
  • 手早く実現したい方
  • ややこしいことが嫌いな方、不精な方
  • とりあえず今だけでも動けばいい、と納得できる方
  • 同業者、友達

問題と基本的対策方法の方針

パソコンを使っての制御や計測、データ解析を行うのですが。一つのことを実現するにも、たくさんの方法があります。いろいろな方法がある中で、私がとる(とりたい)方法は、極力、安く、できるだけ早く、統一された環境で、簡単に開発するです。そこで Python を中心に進めます。くどいようですが Ruby もいいと思います、でも私は、もう Python を始めてしまったので、どうしようもなく困るまで Python です。

安くする為に、LabView、AgilentVee、MatLab、等極力を使わず、でも彼らとのインターフェースを残すぐらい根性無しです。手早くする為に、VBA(Excel)、Cコンパイラ、Java 等を極力使わずに済ませようと思っています。OS も Linux では無く Win32 をベースに進める方針です。

たくさんの言語やシステムを覚えて使いこなす(必要はあるのですが)のを避けたいです。


言い訳


責任を取りたくないので、趣味のブログ、と逃げてます。
知識のレベルが低いので、常識外れの無茶なことをする(してる)かもしれません。
出来るだけ、何も追加で買ったりせず実現する方法を。のつもりですが、つい手元にある有償のモノを使うことがあります。これは手早くの一部なのです。すいません。

Web サイト、Web アプリ の開発とか、文字列や日本語の処理とか、GUI設計、まったく専門外です。
オブジェクト指向プログラミング?とかも、よく意味が分かってません。


ブログを始めた理由

どこのコミュニティーやメイリングリスト等ででも、私のしたいことは 「変」 なので仕方なく。。。自分で。

2008年10月16日木曜日

io.dll で Printer Port の読み込み

前に Python windows で ctypes と io.dll をつかって LPT Port に書き込みましたが、まだデータを読み込んでいませんでしたので、読み込んでみます。環境設定などは、前の記事を見てください。
読み込み自体はうまく行くのですが、表示を期待する形にするのにすこし手間がかかります。

プリンタポートの無い ノートパソコンでは実行しないでください。
プリンターポートのアドレス( 例では0x378)は、parmon.exe などを使って調べて、自分のパソコンのアドレスに合わせてください。
  1. ctypes をインポート
  2. io.dll の IsDriverInstalled という関数の答(1Bit の1か0)をもらって表示します。
  3. プリンターポートに 16進数で 55 の値を io.dll の PortOut 関数で書き込みます。
  4. 自分で書き込んだデータを io.dll の PortIn 関数で読み出します(8ビット)。
自分で「こう表示されるのだろう」と思う値と違う数が表示されるので、ちょっと途惑いますが、落ち着いて考えれば分かりますので、自分が期待する通りの表示になるように対策しましょう。

2.の問題は、1ビットのデータ、「1」の上位が全て 1詰めされて 数値上は -1。
8桁16進表示にすれば FFFFFFFF と 出るかと思うと、そこにも -1。
最下位ビットだけを ビット演算 And で取り出すとやっと思った結果に。

4.の問題は、8ビットだけのデータが欲しいのに上位に不要なデータが入ってるので思わぬ結果に。
16進表示にしても上位にどこから来たのか分からないデータが
16進表示の下二桁だけ表示すればいいかと思いましたが、またおかしい結果に
真面目に8ビットだけ and でマスクしてやっと希望の形になりました。

これは .dll が返す値のフォーマットと Python が受け取る(期待する)フォーマットの食い違いのせいです。本来 argなんとか、で極力合わせて使うべきなのでしょうが、つい(自分にとって)簡単な方向に流れがちで反省しています。将来の課題です。 argなんとか。。。

あぁ、ソースの折り返しがうまく出来てませんね。コピペして見てください。


#
# Win32 (XP)
# Python 2.5
# io.dll c:\windows\system32
#

from ctypes import *


a = 0 # initialize for test
a = windll.io.IsDriverInstalled()
print 'IsDriverInstalled ( decimal ) = ',a
print 'IsDriverInstalled ( 8 digits hex ?) = %(d)08X ' %{'d' :a}
print 'IsDriverInstalled ( 1 digit hex, masked ) = %(d)01X '%{'d' :a & 0x1}


port = 0x378
data_wr = 0x55
windll.io.PortOut(port,data_wr)
print 'PortOut ( 8 digits hex ) = %(d)08X ' %{'d' :data_wr}


data_rd = 0 # initialize for test
data_rd = windll.io.PortIn(port)
print 'PortIn ( decimal ) = ',data_rd
print 'PortIn ( 8 digits hex ) = %(d)08X ' %{'d' :data_rd}
print 'PortIn ( 2 digits hex ?) = %(d)02X ' %{'d' :data_rd}
print 'PortIn ( 2 digits hex, masked ) = %(d)02X '%{'d' :data_rd & 0xFF}