Closed csann closed 9 years ago
Hi csann,
Can you try using the 2.0 branch? The Swift source code in master hasn't been updated for Xcode 7 and Swift 2.0 yet.
I'm hoping to release 2.0 for real on Monday.
Andrew
Branch 2.0 has the same problem. Product/Run is still disabled. I notice that in branch 2.0, Product/Test works whereas in the master I am not sure it did. I’ve deleted my master copy now so I am not sure.
BTW, thank you so much for creating this tool! I am an electrical engineer and like serial port communications. You are obviously a skilled programmer and I am glad you are helping others with useful tools such as this. I plan to study your swift code to learn more about Swift programming.
Clark Sann
On Sep 18, 2015, at 4:01 PM, Andrew Madsen notifications@github.com wrote:
Hi csann,
Can you try using the 2.0 branch? The Swift source code in master hasn't been updated for Xcode 7 and Swift 2.0 yet.
I'm hoping to release 2.0 for real on Monday.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141568169.
Hi Clark,
Give me a bit to look into this, and I’ll figure out what’s going on.
Thanks for the kind words. I too am an electrical engineer, and have always enjoyed software and hardware that work together. I’m glad you’re finding ORSSerialPort useful.
-Andrew
On Sep 18, 2015, at 3:40 PM, csann notifications@github.com wrote:
Andrew
Branch 2.0 has the same problem. Product/Run is still disabled. I notice that in branch 2.0, Product/Test works whereas in the master I am not sure it did. I’ve deleted my master copy now so I am not sure.
BTW, thank you so much for creating this tool! I am an electrical engineer and like serial port communications. You are obviously a skilled programmer and I am glad you are helping others with useful tools such as this. I plan to study your swift code to learn more about Swift programming.
Clark Sann
On Sep 18, 2015, at 4:01 PM, Andrew Madsen notifications@github.com wrote:
Hi csann,
Can you try using the 2.0 branch? The Swift source code in master hasn't been updated for Xcode 7 and Swift 2.0 yet.
I'm hoping to release 2.0 for real on Monday.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141568169.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141575475.
Andrew,
I just found that PacketParsingDemo has the same problem.
Clark
On Sep 18, 2015, at 4:46 PM, Andrew Madsen notifications@github.com wrote:
Hi Clark,
Give me a bit to look into this, and I’ll figure out what’s going on.
Thanks for the kind words. I too am an electrical engineer, and have always enjoyed software and hardware that work together. I’m glad you’re finding ORSSerialPort useful.
-Andrew
On Sep 18, 2015, at 3:40 PM, csann notifications@github.com wrote:
Andrew
Branch 2.0 has the same problem. Product/Run is still disabled. I notice that in branch 2.0, Product/Test works whereas in the master I am not sure it did. I’ve deleted my master copy now so I am not sure.
BTW, thank you so much for creating this tool! I am an electrical engineer and like serial port communications. You are obviously a skilled programmer and I am glad you are helping others with useful tools such as this. I plan to study your swift code to learn more about Swift programming.
Clark Sann
On Sep 18, 2015, at 4:01 PM, Andrew Madsen notifications@github.com wrote:
Hi csann,
Can you try using the 2.0 branch? The Swift source code in master hasn't been updated for Xcode 7 and Swift 2.0 yet.
I'm hoping to release 2.0 for real on Monday.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141568169.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141575475.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141576360.
@csann: I just checked this out. My guess is that when you open one of those projects, after a fresh clone, the (autocreated) scheme for the ORSSerial.framework target is selected by default. Make sure you select the RequestResponseDemo (or PacketParsingDemo) scheme before hitting run.
There are two ways to do this. Either in the dropdown next to the run/stop buttons in the upper left hand corner of the Xcode window:
Or in the Product->Scheme menu:
Let me know if this fixes the problem.
Andrew
This did fix the problem. Thank you for looking into it for me. I was pretty sure it was something simple and I suspected it might be something I did. I’m not yet totally comfortable with Xcode. So thanks again for looking into this. I’m certainly eager to see your completed version 2 library.
I have another thing I want to ask you about. I’m starting on a project to write a chirp-like app to program amateur radios. (I know Chirp already exists, but I don’t like Chirp and just want to write my own. Besides, it a good excuse to learn Swift and possibly functional programming.) I’m using the chirp radio drivers as a guide when writing the communications sections. I’ve already written parsing code which makes it relatively easy to decode the channel data packed into the binary data stream obtained from the radio. I believe my parsing code can be used for all of the radios I currently have and quite possibly for all amateur radios….there are only so many different ways to pack binary data. I expect the unparsing code will be rather trivial, just time consuming. So being bored with parsing and unparsing, last week I decided to jump ahead and write the serial port section. That’s when I found your library.
After examining your library, I’m sure I can use it for communication with these radios, but I’m not sure it is structured in the best way for this purpose.
Let me explain why I say this by first showing you a summary of the procedure required to read data from a TYT TH-UVF-1 2m / 440 handle talkie….
Write “PROG333” Read 1 byte. Verify got an ACK (0x06) back Write STX (0x02) Read 16 byte identification code. Verify get 16 bytes back. Save these bytes for possible verification talking to correct radio type. Write ACK Read 1 byte. Verify got an ACK back. Begin reading blocks of data as follows:
for (i = 0, i < 0x1000, i += 0x10) { Compute and write block read command (8 bytes) Read 20 bytes of radio data. Save away somewhere. Write ACK Read 1 byte Verify got an ACK back } Write 0x45
Any time verification fails, there is no point in continuing on so if this happens, the process must be aborted and restarted. Also, a timeout timer must be used to throw an error if a read command receives no response or an insufficient number of bytes.
I expect the above procedure is pretty simple, especially when compared with what is probably necessary to program a modern DSTAR radio.
If I understand your library correctly, you provide 3 ways to implement this sequence of writes and reads: ORSSerialPortDelegate protocol with serialPort:didReceiveData ORSSerialRequest ORSSerialPacketDescriptor
I will ignore the ORSSerialPacketDescriptor since it is intended for unsolicited messages from an end device. This is not the mode used to program radios.
Using the ORSSerialPortDelegate protocol with serialPort:didReceiveData Send data with the serialPort:sendData. This is very straightforward. To receive data, set up the delegate and wait for didReceiveData to fire indicating that one or more bytes have been received. No timeout provisions are provided so an external timer would need to be implemented. A buffer would need to be maintained to hold the bytes as they arrive. Another function would be needed to check the buffer, determine when a complete message has been received, and then determine what to do next. This is the hard part since there are around 260 different messages. It seems that a state machine needs to be written within the didReceiveData method. I assume there is no reason the state machine could not validate a received message, determine the next message to send, and send it using the sendData method within the didReceiveData method. But I don’t think it is easy and a separate state machine would need to be written for every different radio. Do you have any thoughts on how to accomplish this? Using the ORSSerialPacketDescriptor This is a bit simpler because timeout is already implemented and you also have a implemented a method to validate receive messages using your responseDescriptor. So the list of items I would need to accomplish shrink to…. Send data with the serialPort:sendData. This is very straightforward. To receive data, set up the delegate and wait for didReceiveData to fire indicating that a validated message has been received. Another function would be needed to check the buffer, determine when a complete message has been received, and then determine what to do next…exactly like in 5, above. This method is considerably simpler, I appreciate all the work you have done creating the RequestResponse API, but it still seems difficult to figure out how to move from message to message in a complicated protocol.
It seems to me that your library is too complicated for some use cases. What I would like to see is something like this….
A function to receive data. Something like ...
func receiveData(numberOfBytes: Int) -> NSData
Or maybe the timeout property should more easily be included in the receiveData method like this….
func receiveData(numberOfBytes: Int, timeout: NSTimeInterval) -> NSData?
If this is done the procedure above could be written like this….
serialPort.sendData(“PROG333”) let ack = serialPort.receiveData(1) Verify got an ACK (0x06) back serialPort.sendData(0x02) let ident = serialPort.receiveData(16) Verify get 16 bytes back. Save these bytes for possible verification talking to correct radio type. serialPort.sendData(0x06) ack = serialPort.receiveData(1) Verify got an ACK back. Begin reading blocks of data as follows:
for (i = 0, i < 0x1000, i += 0x10) { Compute block read command (8 bytes) serialPort.sendData(blockReadCommand) let dataBlock = serialPort.receiveData(20) save away somewhere serialPort.sendData(0x06) lack = serialPort.receiveData(1) Verify got an ACK back } serialPort.sendData(0x45)
(I’ve left out timeouts and error handling in the above example)
Or maybe I am making this whole thing too hard. I’m just struggling with how to navigate through a complicated serial port protocol, where there are dozens or maybe thousands of separate read/write transactions. Do you have any advice on the best way to do this with your current library? Would you entertain making changes to your library to make it possible to easier do this?
I won’t be shocked if this is too much for you right now. I know you are probably wanting to focus on version 2. But if you have a second, let me know what you think. I’m going to go ahead and play with the request/response API to learn more about how it works. Maybe it will become clearer after I give it a try.
Clark
On Sep 18, 2015, at 10:03 PM, Andrew Madsen notifications@github.com wrote:
I just checked this out. My guess is that when you open one of those projects, after a fresh clone, the (autocreated) scheme for the ORSSerial.framework target is selected by default. Make sure you select the RequestResponseDemo (or PacketParsingDemo) scheme before hitting run.
There are two ways to do this. Either in the dropdown next to the run/stop buttons in the upper left hand corner of the Xcode window:
https://cloud.githubusercontent.com/assets/1057175/9974353/95e4e5ee-5e48-11e5-838c-a32fefbecb05.png Or in the Product->Scheme menu:
https://cloud.githubusercontent.com/assets/1057175/9974356/9caea6e4-5e48-11e5-87c7-39f9768ba222.png Let me know if this fixes the problem.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141613659.
Hi Clark,
Glad that fixed the problem.
As for the specifics of your application, I think that doing what you're describing with the current ORSSerialPort API is quite feasible. For what it's worth, ORSSerialPort was originally written as part of a fairly sophisticated rig control program with the intention of supporting all the (HF) ham radios out there (even including a provision for loading additional rig "definitions" as fairly simple plugins). That was before I added the request response or packet parsing APIs, of course, but both of those were broadly inspired by what I do in that rig control program.
As I understand it fundamentally you're trying to send a series of commands to the rig, where for each command, you receive a certain number of bytes back before moving on to the next command. This is exactly the kind of scenario the request response API was designed for. The major difference between what you describe and the intended pattern for using ORSSerialRequest is that you're suggesting blocking reads (with a timeout). I like the asynchronous approach much better, for various reasons, but you could actually put a pretty thin layer on top of an ORSSerialPort that "converted" the asynchronous response receipt (or timeout) callback into a synchronous blocking function call.
In either case, you still have to figure out a way to organize your program such that it knows how to move through states for communicating with a radio, and to handle the many different command-sets involved.
I don't know the exact specifics of the communications you're trying to do, but at a high level, I think you do something like:
sendRequest()
, incidentally, not sendData()
). Then, as response data for each request comes back in, it is handed back to the driver for processing.serialPort(requestDidTimeout:)
method, call cancelAllQueuedRequests()
to stop sending further commands, and handle the error however you like.Because ORSSerialPort maintains a queue of ORSSerialRequests, and automatically manages sending each request only after the one before it has succeeded (or timed out), you don't need to manage the full sequence/dependency graph of requests inside serialPort(didReceiveResponse:toRequest:)
, you can "pre sequence" a batch of requests and just hand them off to the port to queue up and send in order.
Some other tips that may help you:
serialPort(didReceiveResponse:toRequest:)
and the next step in your code. The RequestResponseDemo app shows one way to use this to easily discriminate different kinds of requests by stuffing an enum case value into it, then switching on it in serialPort(didReceiveResponse:toRequest:)
. But you can put any (Objective-C compatible) object you'd like in there, including a closure.ORSSerialPacketDescriptor
's responseEvaluator closure can be as simple or complex as you'd like. If you only care that the correct number of bytes was received, you can simply do { $0.length == expectedNumberOfBytes }
. Of course, this won't protect you from corrupt or spurious data being received. If it makes sense, you can also do the actual received data validation inside the response descriptor's response evaluator closure. That way, if the data that comes back isn't as expected, you know immediately (the response will timeout) and can error out. Something more like:{ (data) -> Bool in
return radioTypeFromQueryResponse(data) != nil
}
Anyway, this is all off the top of my head. It's heavily inspired by my own rig control program, which works quite nicely, but has a slightly different purpose than what you describe (it's for real time control, rather than programming memory). I may have missed or messed up some of the details, but I think the basic gist is sound. I should also provide the caveat that I'm an experienced Objective-C programmer, but still a relatively new Swift programmer. Swift opens up some paradigms for programming that I haven't yet had time to fully explore and wrap my head around. There might be a more "swifty" approach to this problem than the one I've described here.
I hope this helps. If you want to discuss further, let's do that via email, if you don't mind. My email address is andrew@openreelsoftware.com.
Andrew
I am having trouble updating my project to V2 of your library. Since I attempted to upgrade, auto complete is not working correctly with ORSSerial.
For example, I cannot use the serialPortWithPath method…it doesn’t pop up in AutoComplete. Instead .init(path: String) appears. This is strange because I don’t see that in your ORSSerialPort.h file.
I suspect that I have mangled my Xcode project structure so I have created a new project and trying to start with your instructions to add ORSSerialPort as a sub-project. That did not solve my problems.
I did notice something that looks strange to me. Is your ORSSerial.h correct? It has these includes…
Is the path ORSSerial is correct. Should it be Source? And should it list the other .h files that are located in Source?
Clark
On Sep 19, 2015, at 9:49 PM, Andrew Madsen notifications@github.com wrote:
Hi Clark,
Glad that fixed the problem.
As for the specifics of your application, I think that doing what you're describing with the current ORSSerialPort API is quite feasible. For what it's worth, ORSSerialPort was originally written as part of a fairly sophisticated rig control program with the intention of supporting all the (HF) ham radios out there (even including a provision for loading additional rig "definitions" as fairly simple plugins). That was before I added the request response or packet parsing APIs, of course, but both of those were broadly inspired by what I do in that rig control program.
As I understand it fundamentally you're trying to send a series of commands to the rig, where for each command, you receive a certain number of bytes back before moving on to the next command. This is exactly the kind of scenario the request response API was designed for. The major difference between what you describe and the intended pattern for using ORSSerialRequest is that you're suggesting blocking reads (with a timeout). I like the asynchronous approach much better, for various reasons, but you could actually put a pretty thin layer on top of an ORSSerialPort that "converted" the asynchronous response receipt (or timeout) callback into a synchronous blocking function call.
In either case, you still have to figure out a way to organize your program such that it knows how to move through states for communicating with a radio, and to handle the many different command-sets involved.
I don't know the exact specifics of the communications you're trying to do, but at a high level, I think you do something like:
Define a high level "RigController" class that only knows how to talk to a radio generically, rather than knowing specific commands for each model. This is the thing that interfaces with the serial port (will be its delegate).
For each model of radio that you want to support, have a class (or struct, or whatever makes sense) that knows the specifics of that radio's command-set. You might call this a "driver" for each radio. This will have functions that provide request(s) to perform various tasks, along with a way to pass received data back in for processing.
The rig controller will get sequences of requests from the current driver, then hand them all off to the serial port (via sendRequest(), incidentally, not sendData()). Then, as response data for each request comes back in, it is handed back to the driver for processing.
If a request timing out is considered a fatal error, in the serialPort(requestDidTimeout:) method, call cancelAllQueuedRequests() to stop sending further commands, and handle the error however you like.
Because ORSSerialPort maintains a queue of ORSSerialRequests, and automatically manages sending each request only after the one before it has succeeded (or timed out), you don't need to manage the full sequence/dependency graph of requests inside serialPort(didReceiveResponse:toRequest:), you can "pre sequence" a batch of requests and just hand them off to the port to queue up and send in order.
Some other tips that may help you:
Make use of ORSSerialRequest's userInfo property. You can use it to "bridge the gap" between serialPort(didReceiveResponse:toRequest:) and the next step in your code. The RequestResponseDemo app shows one way to use this to easily discriminate different kinds of requests by stuffing an enum case value into it, then switching on it in serialPort(didReceiveResponse:toRequest:). But you can put any (Objective-C compatible) object you'd like in there, including a closure.
The "validation" logic in an ORSSerialPacketDescriptor's responseEvaluator closure can be as simple or complex as you'd like. If you only care that the correct number of bytes was received, you can simply do { $0.length == expectedNumberOfBytes }. Of course, this won't protect you from corrupt or spurious data being received. If it makes sense, you can also do the actual received data validation inside the response descriptor's response evaluator closure. That way, if the data that comes back isn't as expected, you know immediately (the response will timeout) and can error out. Something more like:
{ (data) -> Bool in return radioTypeFromQueryResponse(data) != nil } Anyway, this is all off the top of my head. It's heavily inspired by my own rig control program, which works quite nicely, but has a slightly different purpose than what you describe (it's for real time control, rather than programming memory). I may have missed or messed up some of the details, but I think the basic gist is sound. I should also provide the caveat that I'm an experienced Objective-C programmer, but still a relatively new Swift programmer. Swift opens up some paradigms for programming that I haven't yet had time to fully explore and wrap my head around. There might be a more "swifty" approach to this problem than the one I've described here.
I hope this helps. If you want to discuss further, let's do that via email, if you don't mind. My email address is andrew@openreelsoftware.com mailto:andrew@openreelsoftware.com.
— Reply to this email directly or view it on GitHub https://github.com/armadsen/ORSSerialPort/issues/80#issuecomment-141731838.
Hi Clark,
Auto complete not working is probably a sign that the library isn't included in your project correctly, but it could just be Xcode being flaky. You might try deleting the Derived Data folder for the project to force Xcode to reindex. (Window->Projects->Select your project->Click Delete next to Derived Data path).
The #import
statements in ORSSerial.h are definitely correct. ORSSerial.h is a framework master/umbrella header, and as such the individual headers are imported using the convention for importing framework headers. There's only one other header in the project, ORSSerialBuffer.h, and that is intentionally omitted, because it's for a private class not meant to be used except by ORSSerialPort itself.
Do you get build errors when building your project after adding ORSSerialPort, or is the sole problem that autocomplete doesn't work? I'm happy to take a look at your project itself if you want to zip it up and email it to me...
When I attempt to run the Swift version of RequestResponseDemo in Xcode 7.0, the Run button is disabled. I suspect this is due to an error in creating the target, but I am a newby with Swift and Xcode and have not been able to figure out what is wrong yet.