It is currently Wed Oct 21, 2020 3:10 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: Serial Port response
PostPosted: Fri Sep 25, 2020 12:13 am 
Offline
User
User

Joined: Sat Jan 17, 2015 5:24 pm
Posts: 34
I have a PB application running in Windows Pro64. It communicates with an Arduino via a serial port. When the Arduino sends a message, the PB application does not get the message until I move the cursor within the PB window. Is there any way to get the PB application to respond the message as soon as it arrives? I have tried searching the forum as well as the PB documentation and have not been successful in finding anything helpful.


Top
 Profile  
Reply with quote  
 Post subject: Re: Serial Port response
PostPosted: Fri Sep 25, 2020 1:31 am 
Offline
PureBasic Expert
PureBasic Expert
User avatar

Joined: Fri Apr 25, 2003 4:34 pm
Posts: 992
Location: Canada
Sounds like you have an event loop using WaitWindowEvent() insted of WindowEvent()
Anyway, probably best to monitor the port in a Thread.

_________________
Image Image


Top
 Profile  
Reply with quote  
 Post subject: Re: Serial Port response
PostPosted: Fri Sep 25, 2020 9:19 am 
Offline
Addict
Addict
User avatar

Joined: Fri May 12, 2006 6:51 pm
Posts: 2681
Location: Germany
You need Threads and PostEvent...

Here an example with ASCII Kommunikation

Link: viewtopic.php?f=13&t=73812&start=30#p546215

EndOfText is defined as #CRLF$ (13)(10). See InitComport

Update v0.06
Code:
;-TOP

; Comment : Comport Manager Over Thread and Callback
; Author  : mk-soft
; Version : v0.06
; Created : 26.01.2018
; Updated : 25.09.2020

; *****************************************************************************

CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Use Option Threadsafe!"
CompilerEndIf

Prototype ProtoReceiveCB(Text.s)
Prototype ProtoStatusCB(Status, *ComData)

Enumeration
  #ComThread_Stopped
  #ComThread_Startup
  #ComThread_Running
EndEnumeration

Enumeration
  #ComStatus_Nothing
  #ComStatus_OpenPort
  #ComStatus_ClosePort
  #ComStatus_ErrorOpenPort
  #ComStatus_ErrorSend
  #ComStatus_ErrorReceive
  #ComStatus_ErrorDataSize
EndEnumeration

Structure udtComData
  ; Header
  ThreadID.i
  Exit.i
  Status.i
  ; Port Data
  ComID.i
  Port.s
  Baud.i
  Parity.i
  DataBit.i
  StopBit.i
  Handshake.i
  BufferSize.i
  ; End Of Text
  EndOfText.s
  ; Send Data
  SendSignal.i
  SendCount.i
  SendText.s
  SendError.i
  ; Receive data
  ReceiveCount.i
  ReceiveText.s
  ReceiveError.i
  ; Callback
  *StatusCB.ProtoStatusCB
  *ReceiveCB.ProtoReceiveCB
EndStructure

Procedure thComport(*ComData.udtComData)
 
  Protected *Send, *Receive, SendText.s, SendLen, ReceiveText.s, ReceiveLen, Pos
 
  With *ComData
    ; Startup
    \Status = #ComThread_Startup
    \SendCount = 0
    \ReceiveCount = 0
    \ComID = OpenSerialPort(#PB_Any, \Port, \Baud, \Parity, \DataBit, \StopBit, \Handshake, \BufferSize, \BufferSize)
    If \ComID
      \Status = #ComThread_Running
    Else
      If \StatusCB
        \StatusCB(#ComStatus_ErrorOpenPort, *ComData)
      EndIf
      \Status = #ComThread_Stopped
      ProcedureReturn 0
    EndIf
    If \StatusCB
      \StatusCB(#ComStatus_OpenPort, *ComData)
    EndIf
    *Send = AllocateMemory(\BufferSize)
    *Receive = AllocateMemory(\BufferSize)
    ; Loop
    Repeat
      If \SendSignal
        SendText = \SendText + \EndOfText
        SendLen = StringByteLength(SendText, #PB_Ascii)
        If SendLen <= \BufferSize
          PokeS(*Send, SendText, SendLen, #PB_Ascii)
          If WriteSerialPortData(\ComID, *Send, SendLen) = 0
            \SendError = SerialPortError(\ComID)
            If \StatusCB
              \StatusCB(#ComStatus_ErrorSend, *ComData)
            EndIf
          Else
            \SendError = 0
            \SendCount + 1
          EndIf
        Else
          If \StatusCB
            \StatusCB(#ComStatus_ErrorDataSize, *ComData)
          EndIf
        EndIf
        \SendSignal = #False
      EndIf
      ReceiveLen = AvailableSerialPortInput(\ComID)
      If ReceiveLen
        ReceiveLen = ReadSerialPortData(\ComID, *Receive, ReceiveLen)
        If ReceiveLen = 0
          \ReceiveError = SerialPortError(\ComID)
          If \StatusCB
            \StatusCB(#ComStatus_ErrorReceive, *ComData)
          EndIf
        Else
          \ReceiveError = 0
        EndIf
        ReceiveText + PeekS(*Receive, ReceiveLen, #PB_Ascii)
        Repeat
          pos = FindString(ReceiveText, \EndOfText, 1, #PB_String_NoCase)
          If pos
            \ReceiveText = Left(ReceiveText, pos - 1)
            ReceiveText = Mid(ReceiveText, pos + Len(\EndOfText))
            \ReceiveCount + 1
            If \ReceiveCB
              \ReceiveCB(\ReceiveText)
            EndIf
          EndIf
        Until pos = 0
      EndIf
      Delay(10)
    Until \Exit
    ; Shutdown
    CloseSerialPort(\ComID)
    If \StatusCB
      \StatusCB(#ComStatus_ClosePort, *ComData)
    EndIf
    FreeMemory(*Send)
    FreeMemory(*Receive)
    \Status = #ComThread_Stopped
    \ComID = 0
    \Exit = 0
    ProcedureReturn 1
  EndWith
 
EndProcedure

; *****************************************************************************

; Threaded String Helper
Procedure AllocateString(String.s)
  Protected *mem
  *mem = AllocateMemory(StringByteLength(String) + SizeOf(Character))
  If *mem
    PokeS(*mem, String)
  EndIf
  ProcedureReturn *mem
EndProcedure

Procedure.s FreeString(*Mem)
  Protected result.s
  If *Mem
    result = PeekS(*Mem)
    FreeMemory(*Mem)
  EndIf
  ProcedureReturn result
EndProcedure

; *****************************************************************************

CompilerIf #PB_Compiler_IsMainFile
 
  Global ComData.udtComData
 
  Enumeration EventCustomValue #PB_Event_FirstCustomValue
    #My_Event_NewData
    #My_Event_NewState
  EndEnumeration
 
  ; ---------------------------------------------------------------------------
 
  Procedure ReceiveCB(Text.s)
    PostEvent(#My_Event_NewData, 0, 0, 0, AllocateString(Text))
  EndProcedure
  ;---------------------------------------------------
  Procedure MyEventNewDataCB()
    Protected Text.s
    Text = FreeString(EventData())
    AddGadgetItem(0, -1, Text)
    SetGadgetState(0, CountGadgetItems(0) - 1)
    SetGadgetState(0, -1)
  EndProcedure
 
  BindEvent(#My_Event_NewData, @MyEventNewDataCB())
 
  ; ---------------------------------------------------------------------------
 
  Procedure StatusCB(Status, *ComData.udtComData)
    PostEvent(#My_Event_NewState, 0, 0, Status, *ComData)
  EndProcedure
 
  ;------------------------------------------------------------------------
 
  Procedure MyEventNewStateCB()
    Protected Text.s, Status, *ComData.udtComData
    Status = EventType()
    *ComData = EventData()
    Select Status
      Case #ComStatus_OpenPort
        Text = "ComStatus: Open Port " + *ComData\Port
      Case #ComStatus_ClosePort
        Text = "ComStatus: Close Port " + *ComData\Port
      Case #ComStatus_ErrorOpenPort
        Text = "ComError: Open Port " + *ComData\Port
      Case #ComStatus_ErrorSend
        Text = "ComError Send: Port " + *ComData\Port + " - ErrorCode " + *ComData\SendError
      Case #ComStatus_ErrorReceive
        Text = "ComError Receive: Port " + *ComData\Port + " - ErrorCode " + *ComData\ReceiveError
    EndSelect
    If Bool(Text)
      StatusBarText(0, 0, Text)
    EndIf
  EndProcedure
 
  BindEvent(#My_Event_NewState, @MyEventNewStateCB())
 
  ; ---------------------------------------------------------------------------
 
  Procedure InitComport()
    With ComData
      If \Status
        ProcedureReturn 2 ; Always running
      EndIf
      \Port = "COM4"
      \Baud = 115200
      \Parity = #PB_SerialPort_NoParity
      \DataBit = 8
      \StopBit = 1
      \Handshake = #PB_SerialPort_NoHandshake
      \BufferSize = 2048
      \EndOfText = #CRLF$
      \StatusCB = @StatusCB()
      \ReceiveCB = @ReceiveCB()
      \ThreadID = CreateThread(@thComport(), ComData)
      If Not \ThreadID
        StatusBarText(0, 0, "Comport " + \Port + ": Error Create Thread")
        ProcedureReturn 0 ; Error create thread
      Else
        ProcedureReturn 1 ; ok
      EndIf
    EndWith
  EndProcedure
 
  Procedure Main()
    Protected Event, Text.s, i
    If OpenWindow(0, #PB_Ignore, #PB_Ignore, 800, 600, " Test Comport", #PB_Window_SystemMenu)
      CreateStatusBar(0, WindowID(0))
      AddStatusBarField(#PB_Ignore)
      ListViewGadget(0, 0, 0, 800, 540)
      StringGadget(1, 5, 545, 590, 25, "")
      ButtonGadget(2, 605, 545, 90, 25, "Send")
      ButtonGadget(3, 695, 545, 90, 25, "On/Off")
      AddKeyboardShortcut(0, #PB_Shortcut_Return, 1000)
     
      CreatePopupMenu(1)
      MenuItem(101, "Copy ListView")
     
      Repeat
        Event = WaitWindowEvent()
        Select Event
          Case #PB_Event_CloseWindow
            If ComData\Status
              MessageRequester("Info", "Comport Is Open!", #PB_MessageRequester_Warning)
            Else
              Break
            EndIf
           
          Case #PB_Event_Gadget
            Select EventGadget()
              Case 0
                If EventType() = #PB_EventType_RightClick
                  DisplayPopupMenu(1, WindowID(0))
                EndIf
               
              Case 2 ; send button
                If ComData\Status = #ComThread_Running And ComData\SendSignal = #False
                  ComData\SendText = GetGadgetText(1)
                  ComData\SendSignal = #True
                EndIf
              Case 3 ; on/off button
                If ComData\Status
                  ComData\Exit = 1
                Else
                  InitComport()
                EndIf
            EndSelect
           
          Case #PB_Event_Menu
            Select EventMenu()
              Case 101 ; popup menu
                Text = ""
                For i = 0 To CountGadgetItems(0) - 1
                  text + GetGadgetItemText(0, i) + #CRLF$
                Next
                SetClipboardText(Text)
               
            EndSelect
        EndSelect
      ForEver
    EndIf
  EndProcedure : Main()
 
CompilerEndIf

_________________
My Projects ThreadToGUI / OOP-BaseClass / OOP-BaseClassDispatch / EventDesigner V3
PB v3.30 / v5.70 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace


Top
 Profile  
Reply with quote  
 Post subject: Re: Serial Port response
PostPosted: Sat Sep 26, 2020 1:21 am 
Offline
User
User

Joined: Sat Jan 17, 2015 5:24 pm
Posts: 34
Hi Paul and mk-soft,

Thanks for your responses. I tried replacing WaitWindowEvent with WindowEvent, as shown below, based on Paul's 1st suggestion. There was no change in the operation as I described in my post - no bytes received until I move the cursor within the window.
Code:
;Event = WaitWindowEvent()
   Event = WindowEvent()
   If Event = 0
      Delay(10)
      Continue
   EndIf


As far as mk-soft's suggestion... All I would like to do is simply notify the main repeat loop when a byte has been received. I do not need to receive or transmit data in a thread. I am quite new to Windows programming as well as PB so I am a little bit leery of diving into a complex thread implementation.


Top
 Profile  
Reply with quote  
 Post subject: Re: Serial Port response
PostPosted: Sat Sep 26, 2020 6:07 am 
Offline
PureBasic Expert
PureBasic Expert
User avatar

Joined: Fri Apr 25, 2003 4:34 pm
Posts: 992
Location: Canada
I'm sure you understand it is much easier to point someone in the right direction if they post some actual code instead of pseudo code.
Why not post what you have so far?

_________________
Image Image


Top
 Profile  
Reply with quote  
 Post subject: Re: Serial Port response
PostPosted: Sat Sep 26, 2020 10:43 am 
Offline
Addict
Addict
User avatar

Joined: Fri May 12, 2006 6:51 pm
Posts: 2681
Location: Germany
It is never good to process asynchronous communication (like comports) in the event loop of the GUI.
This can be interrupted, for example when the menu window is open. And, there is also no event from comports.

It is always better to program communication in threads. And it's not that bad to work with threads and it's not that difficult to handle them, if you do it right. I also think the handling of structures is an important part of programming and belongs to the basics.

It also makes little sense "I only want to receive one byte". Late when it should be more than one byte the problems start. So first create a concept of how the data should be expanded.

Programming the messages in ASCII in Arduino has the advantage that you can also test them in the terminal. For data I find a better solution than Modbus/TCP (Ethernet), because there are ready made codes for most controllers and the programming of the Modbus/TCP interface is very easy.

Thank to DeepL.com

_________________
My Projects ThreadToGUI / OOP-BaseClass / OOP-BaseClassDispatch / EventDesigner V3
PB v3.30 / v5.70 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace


Top
 Profile  
Reply with quote  
 Post subject: Re: Serial Port response
PostPosted: Sun Sep 27, 2020 8:50 pm 
Offline
User
User

Joined: Sat Jan 17, 2015 5:24 pm
Posts: 34
Hi mk-soft and others,

For now I am taking the easy way out by using a WindowTimer which generates an event every 50msec. I will take a look at threads as I learn more about PB.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
cron

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye