Visual Basic 中学校 掲示板 投稿内容
タグのない投稿を抽出 統計 RSS

Visual Basic 中学校 > 投稿一覧 >

通信データのグラフ化 解決済み

Chart NullReferenceException グラフ タグの編集...

投稿者 素人   (学生)   投稿日時 2019/6/11 13:49:31
[実現させたいこと]
通信データをグラフに"リアルタイム"で表示させたい。

[実現できていること]
・通信データの受信
・グラフへの描画

ここまではできているのですが、通信データから受信する値をリアルタイムで受信したいです。
(リアルタイムといっても、タイムラグが発生するのは承知しています。)

今のプログラムを掲載しますが、
今は、ボタン1をクリックすると、その時に受信したデータを描画し続けるという結果になります。
受信データの変化をグラフに反映させるためには、ボタン1をクリックしなければなりませんが、
これを自動で(ボタン1をクリックしなくて)行えるようにしたいです。


いろいろ試してみたのですが、今の状態が、「実現させたいこと」に一番近い状態です。
どのようにコードを変更すれば良いのでしょうか。


    Private Registers As Integer()
    Private xdata As Double

    Private Sub Button1_Click() Handles Button1.Click

        Dim ComError = 0
        Dim ModbusClient As EasyModbus.ModbusClient = New EasyModbus.ModbusClient(TextBox1.Text, 502)
        Try
            ModbusClient.Connect()
        Catch ex As Exception
            Label10.ForeColor = Color.Red
            Label10.Text = "Error!"
            ComError = 1
        End Try

        If ComError = 0 Then
            Label10.ForeColor = Color.Black
            Label10.Text = "Data..."


            Registers = ModbusClient.ReadHoldingRegisters(0, 3)
            Label1.Text = Registers(0)
            Label2.Text = Registers(1)
            Label3.Text = Registers(2)
            ModbusClient.Disconnect()
        End If



        'Chart背景設定
        'Load時に設定した画像をここで削除し、背景を白に変更
        Chart1.ChartAreas("ChartArea1").InnerPlotPosition.Auto = True

        Chart1.BackImage = ""
        Chart1.BackImageAlignment = ChartImageAlignmentStyle.Center
        Chart1.BackImageWrapMode = ChartImageWrapMode.Scaled

        '背景色設定
        Chart1.BackColor = Color.White
        Chart1.BackImageTransparentColor = Color.Black


        '/////////////////////////////Series設定/////////////////////////////
        '同じスレッドからの呼び出しならそのまま実行
        Chart1.Series.Clear()   '既存の Series をクリアする場合 

        'Dim xaxis, yaxis As Series
        Dim xaxis = Chart1.Series.Add("受信データ")

        '凡例の見た目設定
        Chart1.Legends(0).Font = New Font("MS Pゴシック", 10.0F, FontStyle.Bold)

        '枠線色
        Chart1.Legends(0).BorderColor = Color.Black
        '背景色
        Chart1.Legends(0).BackColor = Color.Yellow
        '枠に影付け
        Chart1.Legends(0).ShadowOffset = 4


        'グラフタイプの設定
        '接続時はとりあえずPoint表示
        xaxis.BorderWidth = 3
        xaxis.BorderDashStyle = ChartDashStyle.Solid
        xaxis.ChartType = SeriesChartType.Point

        '500msec/繰り返しTrueに設定
        TextBox2.Text = CStr(CInt(500))
        CheckBox1.Checked = True

        'Timer1(受信用タイマー)を有効にする
        Timer1.Enabled = True

        '時間をリセット
        time = 0
        '*1000は、msecのため秒表記へ変更
        CountData = CDbl(maxX / (CDbl(TextBox2.Text) / 1000))

        Chart1.Series(0).Points.AddXY(time, xdata)


        xdata = Registers(0)

    End Sub



    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        Chart1.Series("受信データ").Points.AddXY(time, xdata)

        time += CDbl(TextBox2.Text) / 1000

    End Sub

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/11 16:19:57
お使いの Visual Basic のバージョンは何でしょうか。
(コードを見る限り、VB2008 以上ではあるようですが)


> Registers = ModbusClient.ReadHoldingRegisters(0, 3)

EasyModbus のことは全く知らないのですが、検索でヒットするのは下記ですね。
http://easymodbustcp.net/en/modbusclient-methods


> ・通信データの受信
> ・グラフへの描画

データはどのようなタイミングで受信されるのでしょうか。

(1) データが届くたびに、ライブラリからイベント(またはコールバックデリゲート)が呼ばれる形式。
 コールバックは UI スレッドに対して行われる。

(2) データが届くたびに、ライブラリからイベント(またはコールバックデリゲート)が呼ばれる形式。
 コールバックはワーカースレッドに対して非同期的に行われる。

(3) アプリ側から任意のタイミングでデータを取りに行く形式。
  データが無ければ、空のデータが返される。

(4) アプリ側から任意のタイミングでデータを取りに行く形式。
  データが無い場合、新しいデータが届くまで呼び出し元がブロックされる。

(5) その他



> どのようにコードを変更すれば良いのでしょうか。

たとえば (3) であれば、Timer で定期的(たとえば 200ミリ秒ごと)にデータを受け取り、
新しいデータがあれば、それを Chart にプロットするという処理になりそうです。

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/11 19:23:59
> たとえば (3) であれば、Timer で定期的(たとえば 200ミリ秒ごと)にデータを受け取り、
> 新しいデータがあれば、それを Chart にプロットするという処理になりそうです。 

EasyModbus ではないですが…とりあえず、CPU 負荷率を定期的に取得して
それを折れ線グラフにするコードにしてみました。

横軸には、直近 100 回分までの測定値を表示しています。
(100 回を超えた分は、古いデータから削除させています)



Imports System.Windows.Forms.DataVisualization.Charting

Public Class Form1
    Private pcs As Dictionary(Of String, PerformanceCounter)
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Chart1.ChartAreas(0).AxisY.Maximum = 100.0
        Chart1.ChartAreas(0).AxisY.Minimum = 0.0
        Chart1.ChartAreas(0).AxisX.LabelStyle.Enabled = False

        pcs = New Dictionary(Of String, PerformanceCounter)()
        For p = 0 To Environment.ProcessorCount - 1
            Dim key As String = "CPU " & p.ToString()
            pcs.Add(key, New PerformanceCounter("Processor""% Processor Time", p.ToString()))
        Next
        Chart1.Series.Clear()
        For Each kv In pcs
            With Chart1.Series.Add(kv.Key)
                .ChartArea = Chart1.ChartAreas(0).Name
                .ChartType = SeriesChartType.Line
                For n = 1 To 100
                    .Points.Add(0.0)
                Next
            End With
        Next

        'この部分は通常、フォームのデザイン時に設定しておく 
        DirectCast(TrackBar1, System.ComponentModel.ISupportInitialize).BeginInit()
        TrackBar1.TickFrequency = 500
        TrackBar1.SmallChange = 100
        TrackBar1.LargeChange = 1000
        TrackBar1.Minimum = 50
        TrackBar1.Maximum = 2000
        TrackBar1.Value = 500
        Timer1.Interval = 500
        TrackBar1.DataBindings.Add("Value", Timer1, "Interval"False, DataSourceUpdateMode.OnPropertyChanged)
        Label1.DataBindings.Add("Text", TrackBar1, "Value"True).FormatString = "0 ミリ秒間隔"
        DirectCast(TrackBar1, System.ComponentModel.ISupportInitialize).EndInit()

        Timer1.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        For Each kv In pcs
            With Chart1.Series(kv.Key).Points
                .RemoveAt(0)
                .Add(CDbl(kv.Value.NextValue()))
            End With
        Next
    End Sub
End Class


投稿者 素人   (学生)   投稿日時 2019/6/12 10:03:06
回答ありがとうございます。

使用している環境は、Visual Studio Community 2017です。
(記載しておらず、すみませんでした。)

いただいた質問に関する回答ですが、
(3)となります。

Modbus通信で、相手先のデータを受信しています。
相手先のデータは、A/D変換されたデジタル値(0~1023)の値が常に用意されているので、
データがない…という状況は、ないと思っています。

教えていただいたコードの理解がまだできておらず、試せていませんが、
取り急ぎ、回答させていただきます。

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/12 10:59:41
> 相手先のデータは、A/D変換されたデジタル値(0~1023)の値が常に用意されているので、
> データがない…という状況は、ないと思っています。

先の URL によれば、
>> Registers = ModbusClient.ReadHoldingRegisters(0, 3)
のメソッドは、Dim Registers(0 To 2) As Integer 相当の値を返すようですね。

int[] ReadHoldingRegisters(int startingAddress, int quantity)

Read Holding Registers from Master device (Function code 3)

startingAddress:  First holding register to be read
       quantity:  Number of holding registers to be read
        returns:  Int Array [0..quantity-1] which contains the holding registers



ということは、Timer1_Interval 中に ReadHoldingRegisters を呼び出して、
その値をプロットすれば良さそうです。


ちなみに先の私の CPU 負荷なサンプルコードにおいては、
Timer1_Interval 中に、PerformanceCounter クラスの NextValue メソッドを呼び出して
CPU 負荷率(0.0F ~ 100.0F の範囲の Single 型)を得て、
それをプロットするようになっていました。

投稿者 素人   (学生)   投稿日時 2019/6/12 11:33:31
一部コードを書き換えてみました。
書き換えた内容は、
①指定したデータ数取得したら、グラフを一度クリアして、再描画(これを繰り返す)
②通信データのリアルタイム表示

①に関しては、↓のコードでできましたが、②が実装できません…
↓のコードで記述すると、次のエラーが発生してしまいます。
このエラーが起こる原因が分からず、困っています。

//////////////エラーの内容//////////////////////
EasyModbus.Exceptions.ConnectionException
  HResult=0x80131500
  Message=connection error
  Source=EasyModbus
  スタック トレース:
   場所 EasyModbus.ModbusClient.ReadHoldingRegisters(Int32 startingAddress, Int32 quantity)
   場所 Modbus_TCP.Form1.Timer1_Tick(Object sender, EventArgs e) (C:\Users\User01\source\repos\Modbus TCP\Modbus TCP\Form1.vb):行 239
   場所 System.Windows.Forms.Timer.OnTick(EventArgs e)
   場所 System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)
   場所 System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   場所 System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   場所 System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   場所 System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   場所 System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   場所 Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun()
   場所 Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel()
   場所 Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine)
   場所 Modbus_TCP.My.MyApplication.Main(String[] Args) ():行 81
///////////////////////////////////////////////////////
    Private Registers As Integer()
    Private xdata As Double

    Private Sub Button1_Click() Handles Button1.Click

        Dim ComError = 0
        Dim ModbusClient As EasyModbus.ModbusClient = New EasyModbus.ModbusClient(TextBox1.Text, 502)
        Try
            ModbusClient.Connect()
        Catch ex As Exception
            Label10.ForeColor = Color.Red
            Label10.Text = "Error!"
            ComError = 1
        End Try

        If ComError = 0 Then
            Label10.ForeColor = Color.Black
            Label10.Text = "Data..."


            'Registers = ModbusClient.ReadHoldingRegisters(0, 3)
            'Label1.Text = Registers(0)
            'Label2.Text = Registers(1)
            'Label3.Text = Registers(2)
            'ModbusClient.Disconnect()
        End If

        'Chart背景設定
        'Load時に設定した画像をここで削除し、背景を白に変更
        Chart1.ChartAreas("ChartArea1").InnerPlotPosition.Auto = True

        Chart1.BackImage = ""
        Chart1.BackImageAlignment = ChartImageAlignmentStyle.Center
        Chart1.BackImageWrapMode = ChartImageWrapMode.Scaled

        '背景色設定
        Chart1.BackColor = Color.White
        Chart1.BackImageTransparentColor = Color.Black

        '/////////////////////////////Series設定/////////////////////////////
        '同じスレッドからの呼び出しならそのまま実行
        Chart1.Series.Clear()   '既存の Series をクリアする場合 
        'Dim xaxis, yaxis As Series
        Dim xaxis = Chart1.Series.Add("受信データ")
        '凡例の見た目設定
        Chart1.Legends(0).Font = New Font("MS Pゴシック", 10.0F, FontStyle.Bold)
        '枠線色
        Chart1.Legends(0).BorderColor = Color.Black
        '背景色
        Chart1.Legends(0).BackColor = Color.Yellow
        '枠に影付け
        Chart1.Legends(0).ShadowOffset = 4

        'グラフタイプの設定(省略)

        '500msec/繰り返しTrueに設定
        TextBox2.Text = CStr(CInt(500))
        CheckBox1.Checked = True

        'Timer1(受信用タイマー)を有効にする
        Timer1.Enabled = True

        '時間をリセット
        time = 0
        '*1000は、msecのため秒表記へ変更
        CountData = CDbl(maxX / (CDbl(TextBox2.Text) / 1000))

        Chart1.Series(0).Points.AddXY(time, xdata)
    End Sub



投稿者 素人   (学生)   投稿日時 2019/6/12 11:34:32
Timerイベントのコードを追加します。

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        Dim ModbusClient As EasyModbus.ModbusClient = New EasyModbus.ModbusClient(TextBox1.Text, 502)
        Registers = ModbusClient.ReadHoldingRegisters(0, 3)
        Label1.Text = Registers(0)
        Label2.Text = Registers(1)
        Label3.Text = Registers(2)
        xdata = Registers(0)
        Chart1.Series("受信データ").Points.AddXY(time, xdata)

        'Saveコード
        Dim sw As System.IO.StreamWriter
        sw = New System.IO.StreamWriter("rdata.csv", True,
                                              System.Text.Encoding.GetEncoding(932))
        sw.WriteLine(xdata & "," & time)
        sw.Close()


        '秒表記
        time += CDbl(TextBox2.Text) / 1000

        '残りデータ数の表示
        CountData -= 1
        If CountData < 0 Then
            timeLabel.Text = "測定終了"
        Else
            timeLabel.Text = "残り" & Format(CountData, "#") & "個"
        End If

        If CountData < 0 Then

            If CheckBox1.Checked Then
                ''繰り返したい場合は、True
                time = 0
                '*1000は、msecのため秒表記へ変更
                CountData = CDbl(maxX / (CDbl(TextBox2.Text) / 1000))
                System.Threading.Thread.Sleep(300)
                Chart1.Series(0).Points.Clear()

            Else
                Timer1.Enabled = False
                MessageBox.Show("測定終了")
            End If
        End If
    End Sub

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/12 14:01:27
データ型を意識するために、フォームの先頭に
Option Strict On
を記述しておくことをお奨めします。

> EasyModbus.Exceptions.ConnectionException
> HResult=0x80131500
ConnectionException なので、何らかの通信エラーですね。
HResult 値の 0x80131500 は COR_E_EXCEPTION を意味する汎用エラーです。

EasyModbus の仕様は知らないのですが、おそらくは
「既にオープン済みのポートをさらに開こうとした」
「まだオープンしていないデバイスから値を読み取ろうとした」
のどちらかであろうと想像します。


> New EasyModbus.ModbusClient(TextBox1.Text, 502)
第一引数が IP アドレス、第二引数が ポート番号だそうですが、
これって毎回オープンする必要があるのでしょうか?

開き続けておいても構わないのであれば、こんな感じで処理できそうです。


🔷計測開始時(Form1_Load とか Button1_Click とか)は、初期設定のみ。
 🔹Chat1 の凡例を整備
 🔹ModbusClient を New しておく
 🔹Timer1.Start() を呼び出す

🔷Timer1_Tick では、データの取得とグラフ描画
 🔹先ほど New しておいた ModbusClient インスタンスの
  ReadHoldingRegisters を呼び、Integer 配列を得る
 🔹得られたデータを Chat1 にプロットする。
 🔸リアルタイムに得られた Integer 配列の各データを表示させるために
  Label1~Label3 の Text に表示させる必要があるのなら、
  Integer から String への型変換も忘れずに!

🔷計測終了時の処理も必要(Fomr1_FormClosing とか Button2_Click とか)
 🔹Timer1.Stop() を呼び出して、タイマーを止める
 🔹ModbusClient インスタンスはもう使わないので、Disconnect しておく


> ①指定したデータ数取得したら、グラフを一度クリアして、再描画(これを繰り返す)
何故クリアするのでしょう?

凡例(Legends)や系列(Series)については、
デザイン時に設定しておくか、初回設定のみで良い気がするのですが…。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=30287
http://hanatyan.sakura.ne.jp/chart/chart1.htm

リアルタイムに更新させるべきは、プロットされる
ポイントデータ(Points あるいは DataSource) だけですよね。


> ②通信データのリアルタイム表示

どのように表示させるのでしょうか?

➊ 時間の経過と共に、描画されるデータ数がどんどん増えていくもの
➋ データの総数は固定だが、時間ごとにデータが変化するもの
➌ データの最大数が決まっており、それを超えると古いものから消えていくもの

※先の私のサンプルは、上記 ➌ のパターンです。

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/12 14:11:00
> ➊ 時間の経過と共に、描画されるデータ数がどんどん増えていくもの
> ➋ データの総数は固定だが、時間ごとにデータが変化するもの
> ➌ データの最大数が決まっており、それを超えると古いものから消えていくもの

パターン➋の実装例。


Imports System.Windows.Forms.DataVisualization.Charting

Public Class Form1
    Private pcs As Dictionary(Of Integer, PerformanceCounter)
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Shown
        Chart1.ChartAreas(0).AxisY.Maximum = 100.0
        Chart1.ChartAreas(0).AxisY.Minimum = 0.0
        Chart1.ChartAreas(0).AxisX.LabelStyle.Enabled = False

        pcs = New Dictionary(Of Integer, PerformanceCounter)()
        Chart1.Series.Clear()
        For p = 0 To Environment.ProcessorCount - 1
            Dim key As String = "CPU " & p.ToString()
            pcs.Add(p, New PerformanceCounter("Processor""% Processor Time", p.ToString()))
            With Chart1.Series.Add(key)
                .ChartArea = Chart1.ChartAreas(0).Name
                .ChartType = SeriesChartType.Column
                .Points.AddXY(key, 0.0F)
            End With
        Next
        DirectCast(TrackBar1, System.ComponentModel.ISupportInitialize).BeginInit()
        TrackBar1.TickFrequency = 500
        TrackBar1.SmallChange = 100
        TrackBar1.LargeChange = 1000
        TrackBar1.Minimum = 50
        TrackBar1.Maximum = 2000
        TrackBar1.Value = 500
        Timer1.Interval = 500
        TrackBar1.DataBindings.Add("Value", Timer1, "Interval"False, DataSourceUpdateMode.OnPropertyChanged)
        Label1.DataBindings.Add("Text", TrackBar1, "Value"True).FormatString = "0 ミリ秒間隔"
        DirectCast(TrackBar1, System.ComponentModel.ISupportInitialize).EndInit()
        Timer1.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        For Each kv In pcs
            Dim key As String = "CPU " & kv.Key.ToString()
            Chart1.Series(key).Points(0).SetValueY(CDbl(kv.Value.NextValue()))
        Next
        Chart1.Invalidate()
    End Sub
End Class


投稿者 (削除されました)   ()   投稿日時 2019/6/12 16:03:30
(削除されました)

投稿者 素人   (学生)   投稿日時 2019/6/12 16:46:07
魔界の仮面弁士さま
回答ありがとうございます。

私のほうで分かったこととの報告と、質問をさせてください。

①わかったこと
エラーの件ですが、通信タイムアウト時間の設定によることが分かりました。
ご指摘のあった内容をもとに調べていったところ、接続先で設定しているタイムアウト時間より短い時間でTimer1_Tickを実施すると、先ほどのエラーが発生しました。


②処理の流れについて
計測開始時(Form1_Load とか Button1_Click とか)に、ModbusClient を New しておき、
Timer1_Tick では、ModbusClient インスタンスのReadHoldingRegisters を呼ぶように記述しました。
↓のコードです。(コメントアウトしている部分は、Button1_Click内でModbusClientをNewしているので、ここでは、コメントアウトしています。)

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        'Dim ModbusClient As EasyModbus.ModbusClient = New EasyModbus.ModbusClient(TextBox1.Text, 502)

        ModbusClient.Connect()

        Registers = ModbusClient.ReadHoldingRegisters(0, 3)

        Label1.Text = Registers(0)


        xdata = Registers(0)

        Chart1.Series("受信データ").Points.AddXY(time, xdata)

        ModbusClient.Disconnect()


この記述にしてしまうと、Timer1_Tick 内のModbusClientを使用する箇所で、次のエラーが出てしまいます。
"非共有メンバーを参照するには、オブジェクト参照が必要です。"

このエラーを回避するためには、Timer1_Tick 内でもModbusClientをNewするしかありません。
何か根本的に間違えているのでしょうか。 


③受信データの値に応じてイベントを発生させたい。

例えば、受信データが800を超えたときにメッセージを表示させます。
当たり前ですが、Timer1_Tick 内に記述してしまうと、800を超えている間、ずっとメッセージが表示されてしまいます。

        If xdata > 800 Then
            MessageBox.Show("800を超えました!")
        End If

これを、ある値を超えたら、1度だけメッセージを表示させるためには、どうすればよいのでしょうか。

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/12 17:09:33
> "非共有メンバーを参照するには、オブジェクト参照が必要です。"

『非共有メンバー(インスタンス メンバー)』と
『共有メンバー』の違いはわかりますか?


TextBox クラス の 「Focus メソッド」や「Text プロパティ」は『非共有メンバー』ですし、
MessageBox クラスの「Show メソッド」は『共有メンバー』です。



フォームにラベルを貼って、そこにデータを書き込む場合を考えてみると、

 Label1.Text = "ABC"
 Label2.Text = "XYZ"

のように書くかと思いますが、これを

 Label.Text = "ABC"

とは書けませんよね。 

「どの Label インスタンスの Text プロパティを操作しているか」を示さねば、
どのラベルが操作されるのか分からなくなってしまいます。

ModbusClient の場合も同様です。Shared か否かを意識してコーディングしてみてください。



たとえば ConvertRegistersToDouble メソッド等は「共有」メンバーらしいので、

Dim x As Integer() = {0, 0}
' 『クラス名 . メンバー名』で呼び出す 
Dim y As Double = ModbusClient.ConvertRegistersToDouble(x)

のような構文で呼び出せそうですが、ReadHoldingRegisters メソッドは「非共有」メンバーらしいので、

Dim modbusClient As New ModbusClient(ipAddress, port)
'''' ** 中略 ** 
' 『変数名 . メンバー名』で呼び出す 
Dim z As Integer() = modbusClient.ReadHoldingRegisters(startingAddress, quantity) 

などとせねばならないでしょう。


掲示板の左上「Visual Basic 中学校」のリンクから、
「初級講座」が辿れるので、そちらも参考にしてみてください。

Visual Basic 中学校 > 初級講座 > 第9回 クラスの使い方 > 5.メンバの種類
http://rucio.a.la9.jp/main/dotnet/shokyu/standard9.htm

Visual Basic 中学校 > 初級講座 > 第48回 高度なメソッド・プロパティ
http://rucio.a.la9.jp/main/dotnet/shokyu/standard48.htm



> Registers = ModbusClient.ReadHoldingRegisters(0, 3)

それだと、
「ModbusClient クラスの ReadHoldingRegisters を呼んでいる」
ことになりますよね。先の私の回答は、
「ModbusClient インスタンスの ReadHoldingRegisters を呼び」
だったはずですよ。


ReadHoldingRegisters メソッドは非共有なメソッドですので、
ModbusClient という「データ型そのもの」に対して呼び出すのではなく、
「ModbusClient 型の変数」に対して呼び出す必要があります。


それともう一つ。
代入式の左辺にある、メソッドの戻り値を受け取るための Registers 変数は
どこに宣言していますか? この変数の宣言文も見せてください。


> ModbusClient.Connect()
> ModbusClient.Disconnect()

EasyModbus 環境が手元に無いので検証はできないのですが、
上記の Connect / Disconnect は共有メソッドなのでしょうか。

最初に提示頂いたコードでは共有メソッドとして呼び出していたようなので、
恐らく大丈夫だとは思うのですが…検索でヒットした ModbusClient クラスの実装を見ると、
非共有なメソッドのように見受けられたので、念のために確認しています。

投稿者 素人   (学生)   投稿日時 2019/6/13 08:49:35
共有、非共有についての認識ができていませんでした。
勉強になります。

Button1.ClickとTimer_Tickそれぞれに、New EasyModbusがありましたので、
↓のように、Privateで宣言してみました。(いただいたコメントに対する、私の理解が低くてすみません。)

    Private ModbusClient As EasyModbus.ModbusClient = New EasyModbus.ModbusClient(TextBox1.Text, 502)

    Private Sub Button1_Click() Handles Button1.Click

  ~~省略~~

  End Sub


すると、↓のエラーメッセージが出てしまいます。


System.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'

Modbus_TCP.Form1.TextBox1.get が Nothing を返しました。


投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/6/13 11:07:15
> System.NullReferenceException: 'オブジェクト参照がオブジェクト インスタンスに設定されていません。'

TextBox1 が実体化(インスタンス化)される前に、
TextBox1.Text を呼び出そうとしているからです。

変数の宣言部とインスタンスの生成部を分けましょう。

' 変数の名前がクラスの名前と同じなのはややこしいので、 
' 変数名は少し変更しました 
Private modbusClientObject As EasyModbus.ModbusClient

' 生成するタイミングは、Form1_Load でも Button1_Click でも構いません 
Private Sub ~~~
    modbusClientObject = New EasyModbus.ModbusClient(TextBox1.Text, 502)
End Sub




デザイン時にフォームに貼った TextBox1 も、プログラムからみれば
「TextBox 型の変数」に過ぎないことはご存知でしょうか。

また、その TextBox1 (のインスタンス)がどのタイミングで生成されるのかも
把握しておいてください。



投稿者 素人   (学生)   投稿日時 2019/6/17 14:34:14
魔界の仮面弁士さま

ありがとうございました。
時間がかかりましたが、やっと理解ができました。

実体化(New)(した後は、modbusClientObject.xxxxxxとして使用することが分かりました。

投稿者 素人   (学生)   投稿日時 2019/9/27 13:58:21
魔界の仮面弁士さま

時間が経過していて申し訳ないのですが…
以前質問させていただいた内容について教えていただきたいことがあります。


最終的に、↓のように定義しましたが、
※(EasyModbus.ModbusClient)部で次のエラーが発生していることに気が付きました。
動作はしているのですが、気になるので解決したいと考えています。
何か原因がありそうでしたら、指摘していただけると助かります。


//////////////////////////////////////////////////////////////////////////////

' 変数の名前がクラスの名前と同じなのはややこしいので、 
' 変数名は少し変更しました 
Private modbusClientObject As EasyModbus.ModbusClient (※)

' 生成するタイミングは、Form1_Load でも Button1_Click でも構いません 
Private Sub ~~~
    modbusClientObject = New EasyModbus.ModbusClient(TextBox1.Text, 502)
End Sub
//////////////////////////////////////////////////////////////////////////////

◆エラーの内容
EasyModbus.ModbusClient部に赤い下線(波線)で、
"型 'EasyModbus.ModbusClientは定義されていません。"と表示されます。

◆ビルドの結果は特に問題ありませんが、
デバッグ時の出力モニタには次のメッセージが表示されます。
'Modbus TCP.exe' (CLR v4.0.30319: DefaultDomain): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: DefaultDomain): 'C:\Users\User01\source\repos\Modbus TCP\Modbus TCP\bin\Debug\Modbus TCP.exe' が読み込まれました。シンボルが読み込まれました。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.VisualBasic\v4.0_10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualBasic.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Drawing.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.Remoting\v4.0_4.0.0.0__b77a5c561934e089\System.Runtime.Remoting.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms.DataVisualization\v4.0_4.0.0.0__31bf3856ad364e35\System.Windows.Forms.DataVisualization.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms.DataVisualization.resources\v4.0_4.0.0.0_ja_31bf3856ad364e35\System.Windows.Forms.DataVisualization.resources.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Accessibility\v4.0_4.0.0.0__b03f5f7f11d50a3a\Accessibility.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
'Modbus TCP.exe' (CLR v4.0.30319: Modbus TCP.exe): 'C:\Users\User01\source\repos\Modbus TCP\Modbus TCP\bin\Debug\EasyModbus.dll' が読み込まれました。PDB ファイルを開けないか、ファイルが見つかりません。
EasyModbus Client Library Version: 5.5.0.0

投稿者 素人   (学生)   投稿日時 2019/9/27 14:09:22
魔界の仮面弁士さま

失礼しました。
参照dllファイルの場所を変更していたがために発生していたエラーでした。

基本的なことをお聞きしますが、アプリケーションとしてリリースする場合は、参照dllは常に同じフォルダ内に入れておかなければならないのでしょうか。

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/9/27 17:40:02
> 参照dllは常に同じフォルダ内に入れておかなければならないのでしょうか。

基本的にはそうです。

DLL にも幾つか種類がありますが、今回のものは下記の③ですね。

① Declare ステートメント または DllAttribute 属性で指定して使う DLL
② 参照設定して使う DLL - ActiveX DLL (COM コンポーネント)
③ 参照設定して使う DLL - .NET Managed
④ リソース情報のみを持つ DLL


③ を参照設定して使う場合、その置き場としては
 ➊ アプリケーションと同じフォルダ
 ➋ グローバル アセンブリ キャッシュとして登録された場所
のいずれかとなります。

厳密にバージョンが管理される公式のファイル(System.Data.dll など)は ➋ に
置かれていたりもしますが、追加のライブラリについては ➊ に配置させるのが普通です。

投稿者 素人   (学生)   投稿日時 2019/10/4 16:53:31
魔界の仮面弁士さま

分かりやすい回答ありがとうございました。