Beijer Electronics (formerly QSI Corporation)

Manufacturer of Mobile Data and Human Machine Interface Terminals.
It is currently Fri Nov 24, 2017 12:10 am

All times are UTC - 7 hours




Post new topic Reply to topic  [ 6 posts ] 
Author Message
PostPosted: Thu Dec 03, 2009 9:42 am 
Offline

Joined: Thu Dec 03, 2009 9:18 am
Posts: 15
Hi -
We are using a G55 terminal with an Qlarity BASIC app that involves serial communication, and I have found the event-driven way of dealing with responses to be very awkward, and I would like to know if there is a better way.

To explain my trouble, I will describe an sample function that we would like to write. (This is not a made up example!) Our app involves motion control, and we use the serial port to send data to a motor controller, and receive responses. We would like to have a function to get the current position of the motor. To do this, we need to send a specific command through the serial port, and then the response from that command contains the data we want.

I want to write my function so that it sends the request, and the function does not return until the response has been recieved (or times out). So the function would look like:

func get_position () returns string
dim response as string
' send command to request position
DelimitedComm_1.SendData = "g r0x32"
' now I want to wait for the response from the serial port
' following command does not exist!
response = DelimitedComm_1.GetData
return response
endfunc

However, as far as I know, I can't write a function like that. What I have to do, is send the request, and then wait for the RecvdDataPacket event to trigger from the response. But I can't do anything inside of my get_position() function to cause the event processing to occur (that I know about), and so I can't make it so that the response is received before my get_position() function returns. As a result, I cannot structure the program to have a straightforward flow of control. (Whoever wants to call get_position() needs to have the answer before it can continue with the rest of its logic.)

I hope I have explained my situation ok with this, I can clarify further if needed.

Thanks,
Mark K


Top
 Profile  
 
PostPosted: Thu Dec 03, 2009 9:55 am 
Offline
QSI Support
QSI Support
User avatar

Joined: Wed Mar 08, 2006 12:25 pm
Posts: 881
Location: Salt Lake City, Utah
You have hit on what is currently an active research topic here.

I do understand what you are asking, and you are certainly not the first person to point out how awkward the current system is when dealing with what I call "interactive" communication protocols (i.e. any time you send something to the device and need to wait for a response).

The current design was not an accident. To take your example:
Code:
func get_position () returns string
    dim response as string
    ' send command to request position
    DelimitedComm_1.SendData = "g r0x32"
    ' now I want to wait for the response from the serial port
    ' following command does not exist!
<WHAT HAPPENS HERE?>
    response = DelimitedComm_1.GetData
    return response
endfunc


When we were designing the original Qlarity runtime, we had to answer the question "What happens here?" when faced with this type of code.

We came up with three possible answers:
--1: Block and wait for a response.
--2: Pause execution of the current method and let other Qlarity code run
--3: Disallow that type of construct

The first one was distasteful as blocking could potentially lock up your user interface for seconds on end, and we had to introduce the concept of timeouts if the other side never responded.

The second solution we rejected because we didn't want to introduce threads to Qlarity (which is intended as a user interface that folks new to programming can use). We could have gone with the VB6 DoEvents paradigm, however that has its own set of warts and I have used a LOT of VB written interfaces that did not properly handle reentrancy from DoEvents loops.

Finally we went with the third solution. At the time it seemed like that would be the best solution overall. For the next major iteration of Qlarity, I am leaning more towards a DoEvents style interface, but with built in safety against reentrancy. This would allow advanced users to write interactive functions without blocking the UI.

For a more extensive discussion on this topic, you can read this FAQ: viewtopic.php?f=4&t=164

_________________
Jeremy
http://www.beijerinc.com


Top
 Profile  
 
PostPosted: Thu Dec 03, 2009 10:10 am 
Offline

Joined: Thu Dec 03, 2009 9:18 am
Posts: 15
Thanks for your quick response. I'll suggest a possible fourth option...

If there was some way I could query the serial port, and immediately receive either:
A value indicating that there is no serial port input available, or
The available serial port data.

I could build the interface that I would like. (This is basically how I do this in C++ Win32 apps.)
Such a function would return right away, it would have no need to wait. Therefore this wouldn't block any other processing. But then I could write my own wait_for_response () function, which would keep querying until it got some data, or gave up (timeout).

*MY* new function would then have the blocking issues you mention, but since I made it myself, I am presumably prepared to deal with the consequences. But there would be no problems created for any other Qlarity users, as this scheme would require specific user written code to bring up the blocking possibility.

- Mark K


Top
 Profile  
 
PostPosted: Thu Dec 03, 2009 10:21 am 
Offline
QSI Support
QSI Support
User avatar

Joined: Wed Mar 08, 2006 12:25 pm
Posts: 881
Location: Salt Lake City, Utah
We did consider the 4th option as well -- although it is, in my opinion more of a variant of the other three -- since the blocking issue still has to be considered, it just gets moved.

Technically, this is more complex than it appears at a blush. When serial data arrives it is briefly buffered by the firmware, then placed in the message queue as a MSG_COMM_RECEIVE. Your solution would probably require that the message queue functionality be disabled (which could be done for serial ports, but would be more tricky for network connections). Otherwise the GetCommRxData() function would have to search the message queue as well as the internal firmware buffer, which would impact its performance.

If we had a way for an object to tell the firmware to continue processing messages, (i.e. DoEvents) it would be possible to write a communication object that behaves the way you describe in your 4th option.

_________________
Jeremy
http://www.beijerinc.com


Top
 Profile  
 
PostPosted: Sun Dec 06, 2009 1:59 pm 
Offline

Joined: Thu Dec 03, 2009 9:18 am
Posts: 15
I really think that QSI should consider option 4 as a reasonable choice.

To be honest, we consider the current situation with serial communications as not acceptable.
Our current app muddles through without really having reliable serial communication, and this is not so good.
I wasn't around when the decision to use QSI as a vendor was made, but if we made this decision now,
I would have to rate the QSI environment as 'not acceptable' and look for alternative vendors.
(If I didn't already have so much to do, I might even consider such a change now!)
At the least, the next time I evaluate a vendor for such stuff, the #1 test will be to try and implement
something like the get_position() function that I described, and if I can't do it, we will move on to the next vendor.

I haven't really done that much serial programming, but all that I have done has basically involved
a device that says something like 'ok' in response to a command, and it is clearly good programming practice
to notice and handle this, to both keep in sync with the state of the device on the serial line, and also
notice if it quits responding. With the current system, this is basically impossible to do. I think it follows
that, necessarily, *NO* serial communications app written with the QSI system is well implemented, whether or
not a detailed analysis of the response is needed.

You are correct, of course, that the DoEvents method has signficant issues, in that all kinds of other events
can fire, and it is at least difficult to make sure that none of these can cause subtle problems. But if the
app can make an explicit check to see if there is input available, there is no need to handle the general event
problem. YES, it is annoying that the app will be basically unresponsive while it is waiting, but this is
much less important than correctness!

It could (potentially) be possible to avoid the potential unresponsiveness without having a thread system.
For example, I have implemented something like the following many times in C++ systems:

Each different thing has an update function (which might be passed the elapsed time dt).
There is then a main loop that might look something like:

function main_loop
while true
' get elapsed time
t_now = current time
t_elapsed = t_now - t_prev
t_prev = t_now

' call update on all things
loop over all things:
thing->update (t_elapsed)

' process events
DoEvents
wend
endfunc

The serial port object would then be responsible for, when it sends commands, to immediate end its current update(). On the subsequent update() calls, it would then check the serial port state, and if data was available, it would read it, and then continue with what it started in a previous update. Although this complicates things somewhat, it does allow the program to continue to be responsive, while letting the serial comm stuff properly handle responses, while still allowing events to be handled in a non-disruptive way.

Unfortunately, I don't know how to implement such a main loop in Qlarity, or I would already have tried this.

I do appreciate that you are reading my comments.

Thanks,
Mark Kness


Top
 Profile  
 
PostPosted: Tue Dec 08, 2009 9:28 am 
Offline
QSI Support
QSI Support
User avatar

Joined: Wed Mar 08, 2006 12:25 pm
Posts: 881
Location: Salt Lake City, Utah
Dear Mark,

I understand you are frustrated with Qlarity -- and reasonably so for the task you want to accomplish. I have written a lot of Qlarity code, and a very large number of communication interfaces.

Two of the most difficult (read: obnoxious) programming problems to solve in Qlarity are the "interactive communications" problem where you need to send data to a device and wait for a response before your program logic can continue; and the "modal UI" problem where you need to present a UI to the user and wait for a response before program logic can continue.

In my mind these are simply two manifestations of the same issue: that you must unequivocally release program flow control back to the firmware for event code to progress.

As you have pointed out, one solution would be a "Give me all pending data on this communication channel" command. Please trust me that from a technical standpoint this is difficult to implement in the general case. The general solution would likely have an O(N) execution time with regard to the size of the Qlarity message queue or require a major rewrite of the communication subsystem, neither of which is very appealing to me. Also it doesn't solve the Modal UI issue either.

I am leaning towards a DoEvents type solution in the future, but of course this does not help you now. (DoEvents would allow a communication object to present a GetPendingData() function).

I would like to present some ways that I have worked around the interactive communication issue in the past. I am aware that none of these are extremely compelling for what you are trying to do. I am also aware of how complicated some of these can get when you make them extremely general, just look at the MBCommV2 object to see an example.

---------------------------------------------

If you have a limited number of data items that are interested in from your remote device, you can work on a "prefetch-and-cache" system. Here is one way you can implement this (these examples will omit a lot of error checking and plumbing code so that the methods can be emphasized)

Code:
'Global code
startType QueryItems as integer
    QueryPos := 0
    QueryTemp := 1
    QueryError := 2

    QueryInc := 1
    QueryCount := 3
endtype

'In your communication object
dim curQuery as QueryItems
init curQuery := queryError

dim channel as comm
dim buffer[] as byte

dim cachePosX as integer
dim cachePosY as integer
dim cacheTemp as float
dim cacheError as string

func GetPos(x as reference to integer, y as reference to integer)
    x = cachePosX
    y = cachePosY
endfunc

func GetTemp() returns float
    return cacheTemp
endfunc

func GetError() returns string
    return cacheError
endfunc

dim queryPending as boolean
func QueryNext()
    if queryPending then
        return
    endif

    curQuery := (curQuery + QueryInc) mod QueryCount
    if curQuery == QueryPos then
        transmit(channel, "gp;\r", false)
    elseif curQuery == QueryTemp then
        transmit(channel, "gt;\r", false)
    elseif curQuery == QueryError then
        transmit(channel, "ge;\r", false)
    endif
    queryPending = true
endfunc

func CommReceive (data[] as byte) returns boolean
    handles MSG_COMM_RECEIVE
    dim p1, p2 as integer

    buffer := buffer + data

    if curQuery == QueryPos then
        p1 = find(buffer, 0, -1, ",")
        p2 = find(buffer, p1+1, -1, "\r")
        if p2 < 0 then
            return true
        endif
        val(cachePosX, left(buffer, p1))
        val(cachePosY, mid(buffer, p1+1, p2-p1-1))
    elseif curQuery == QueryTemp then
        p1 = find(buffer, 0, -1, "\r")
        if p1 < 0 then
            return true
        endif
        val(cacheTemp, left(buffer, p1))
    elseif curQuery == QueryError then
        p1 = find(buffer, 0, -1, "\r")
        if p1 < 0 then
            return true
        endif
        cacheError = left(buffer, p1)
    endif

    buffer := ""
    queryPending = false
    QueryNext()
    return true
endfunc


Another option is what I think of as an asynchronous procedure call

Code:
'Global code
const message RequestComplete

'Some object that
func StartPostionQuery()
    if not commObj.SendQuery("gp;\r", me, 1) then
        'Comm Obj is busy right now.  A more sophisticated version could queue requests and not return false here
        'Do whatever error stuff is correct
    endif
endfunc

func ProcessPositionQuery(parm as integer) returns boolean
    handles RequestComplete

    dim x, y as integer
    dim data[] as byte
    dim p1, p2 as integer

    if parm == 1 then
        if not commObj.WasLastRequestSuccessful() then
            'Do error handling here
        endif

        data = commObj.GetResults()

        p1 = find(data, 0, -1, ",")
        p2 = find(data, p1+1, -1, "\r")
        if p2 < 0 then
            'Do error handling here
        endif
        val(x, left(buffer, p1))
        val(y, mid(buffer, p1+1, p2-p1-1))

        'Do whatever you need to with the actual position here
    endif
    return true
endfunc

'In your comm object
dim pending as boolean
dim channel as comm
dim buffer[] as byte
dim resultBuf[] as byte
dim myResultObj as objref
dim myResultParm as integer

func SendQuery(data[] as byte, resultObj as objref, resultParm as integer) returns boolean
    if pending then
        return false
    endif

    transmit(channel, data, false)
    pending = true
    myResultObj := resultObj
    myResultParm := resultParm

    return true
endfunc

func WasLastRequestSuccessful() returns boolean
    return not pending
endfunc

func GetResults() returns byte[]
    return resultBuf
endfunc

func CommReceive (data[] as byte) returns boolean
    handles MSG_COMM_RECEIVE

    dim pos as integer
    buffer = buffer + data
    pos = find(Buffer, 0, -1, "\r")
    if pos < 0 then
        return true
    endif
    resultBuf = left(buffer, pos)
    pending = false

    UserDirectMsg(myResultObj, RequestComplete, myResultParm, true)

    return true
endfunc

_________________
Jeremy
http://www.beijerinc.com


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

All times are UTC - 7 hours


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
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group