FactbirdHQ / atat

no_std crate for parsing AT commands
Apache License 2.0
116 stars 32 forks source link

Doing a simple modem example with ATAT #148

Closed mschnell1 closed 1 year ago

mschnell1 commented 1 year ago

Need for improved documentation and/or examples.

(In the end, I want to use ATAT in an embedded project to implement a modem communication with an MQTT server, but of course at first I want to test simple stuff. ) Trying to use the best possible approach, seeing there were some relevant changes vs 0.18, I did some tokio enabled example code using the current "master" branch, on a Windows PC using the nightly rust build. in fact - with a help of a more educated colleague - I got something running. in fact I can client.send ATE0 to the modem, which answers OK and I get a result "Received OK (6/6)". That is fine. But when trying to do a simple command that expects a result string (e.g. ATI or ATS0=?) I get Err(Parse). (I see the correct output of the modem in the ATAT trace.) In fact, I am not astonished about that, as I did

static BUFFERS: Buffers<at::Urc, .... 
and 
let (ingress, client) = BUFFERS.split(
           Tx { tx: tx_writer },
           atat::DefaultDigester::<at::Urc>::new(),
           atat::Config::default(),

to get a digester. (Using some fake at::Urc srtuct

#[derive(Clone, AtatUrc)]
pub enum Urc {
    #[at_urc("+QMTRECV")]
    MqttOnReceived(mqtt::on_receive::OnReceived),
}

and of course the "default" ("Urc") parser (using serdes) is not appropriate for handling the outputs of ATI and ATS0=? (last is 000).

Hence I'd like to do/see an example (not using this type of "Buffers", which seemingly needs AtatUrc to be implemented) and/or documentation on how to do such "simple Stuff" with ATAT to get started.

Thanks for your great work !

mschnell1 commented 1 year ago

I now found the "Manufacturer identification" example in the master branch repository, trying to get this going ... In my example project, I could insert, compile and execute the "ManufacturerId" code from this example but now get Err(Error)instead of Err(Parse) when sending ATI (the modem I use returns ERROR on AT+CGMI). (I see the correct output of the modem in the ATAT trace.) Weird: the string sent to the modem seems to be ATI="" .

Now researching on how the example on GitHib avoids "Buffers"...

MathiasKoch commented 1 year ago

Hi.

Sorry about the missing documentation. I am not quite sure that I understand the actual issue you are facing, though?

the modem I use returns ERROR on AT+CGMI

This seems unrelated to ATAT? In that case your modem probably doesn't support +CGMI?

Now researching on how the example on GitHib avoids "Buffers"...

But, it doesn't? And why would you avoid Buffers? https://github.com/BlackbirdHQ/atat/blob/5a06fed2ee842c63619e397b572f5716d4be1b7f/examples/src/bin/std-tokio.rs#L19-L20

mschnell1 commented 1 year ago

Thanks for the Reply !! Of course using the work in progress, I don't expect perfect documentation, but am very happy to be able to take advantage of the crate.

I now did test AT+GMI instead of AT+CGMI and this does work perfectly with my example code. (Sorry that I did not have checked this before).

So I rephrase the "issue" I see (not yet really knowing if the ATAT crate is the correct addressee for same, or something else related might be not happy with what I try).

OK, you state I should not try to avoid Buffers. In fact I would not like to avoid it, but Buffers needs AtatUrc to be bound. And when using AtatUrc, the Urc Parser is called for the result and retunrs with an error in all tests I did before today.

I did intsall my on parser (using Buffers as before but doing my own struct to be submitted as a generic for the DefaultDigester)

    let (ingress, client) = BUFFERS.split(
           Tx { tx: tx_writer },
           atat::DefaultDigester::<at::ModemTypeParser>::new(),
           atat::Config::default(),
       );

This in fact does call my parser, but afterwards the Urc parser is calld and again returns the error.

But the general "issue" (or misunderstanding at my side) is, that I assume that I should do something like let res = client.send(&at::modem::test::QueryXXX {}).await; to communicate with the modem, providing multiple QueryXXX structs (created by #[derive(Clone, Debug, AtatCmd)] and #[at_cmd("...")] ) for the different communication tasks.

But before being able to do that, I need to instanciate ingress and for that use Buffers that needs to be givemn a single dedicate such QueryXXX struct. This currently does make sense for me.

Moreover I think I should be able to have mutiple parsers (or parser configurations) for dfferent modem commnications. I understand that BUFFERS is static and should not be instanciated multiple times. So it would be necessary to swap the parser on the fly. Does that make any sense ?

Thanks for listening ! -Michael

MathiasKoch commented 1 year ago

I think I see where you go wrong in this :)

The generic you provide to Buffers is required in order to parse URC's (Unsolicited response codes) See https://www.developershome.com/sms/resultCodes3.asp for info around what URC's are, if you are unfamiliar with AT command sets.

But the general "issue" (or misunderstanding at my side) is, that I assume that I should do something like let res = client.send(&at::modem::test::QueryXXX {}).await; to communicate with the modem, providing multiple QueryXXX structs (created by #[derive(Clone, Debug, AtatCmd)] and #[at_cmd("...")] ) for the different communication tasks.

The QueryXXX structs earch represent a single AT command with their respective arguments, and a response struct given in the #[at_cmd("...")].

When you call .send(..).await with such a request struct, it will convert it into an at string, for example AT+CGMI\r\n, and wait for the response to arrive from the modem, terminated in a response code (Usually OK or ERROR)

If you do not wish to be able to parse URC's, you can just pass it an empty enum with derive(AtatUrc), and it won't ever match an URC.

mschnell1 commented 1 year ago

Thanks a lot ! I'll continue researching ... I seem to understand. The Modem answer (other than OKof ERROR ?? ) to any command is checked by the URC Parser, even though the goal of same is to handle unsolicited messages, spontanously sent by the Modem. Maybe it does not make senses to do such Modem communication while unsolicited message might be coming. Could it make sense to be able to switch the detection of unsolicited Messages on and off on the fly (without reinstanciating BUFFERS ) to allow for temporary biderectional Modem communication ?

BTW.: Obviously the URC parser does not like strings with leading Zeros and strings containing a :. Seemingly such is not expected in an unsolicited modem message.

BTW2: The Modem I use responds to AT+QISTATE=? by several different strings. Those are not compatible with parsing by the URC Parser. If I use a not empty response struct, even an answer OKis detected as an error (while with AT+CGMI a "normal" string is accepted).

mschnell1 commented 1 year ago

Aftesr some discussions and researching, maybe I have a glimps of what is happening (or should be happening)

==

Am I correct ? Now the issue I face is that after doing client.send(..).await; in fact both parsers are called (first the Digestr's and afterwards the Buffer's (URC) parser) and client.send(..).await; returns the result provided by the URC parser (which is Error() in most cases). I feel the result of the URC parser should be used to ignore or handle the would-be URC string , and (if it's not a valid URC string "arm") the result of the digester's parser should be provided as a restult of client.send(..).await; .

Thansk for listing ! -Michael

MathiasKoch commented 1 year ago

Am I correct ?

You are absolutely correct :+1: Couldn't have written it better :)

Now the issue I face is that after doing client.send(..).await; in fact both parsers are called (first the Digestr's and afterwards the Buffer's (URC) parser) and client.send(..).await; returns the result provided by the URC parser (which is Error() in most cases).

This does not sound like it is handled correct.. Not sure why that happens, though. Maybe @rmja has some insights with the newer URC handling?

mschnell1 commented 1 year ago

Further research: I seem to have found out that trying to change the parser for results of commands to be sent by doing a different implementation of the digester was a bad idea. Instead I need to impl AtatCmd without deriving. (That already can be found in the examples in the current documentation, which I obviously was not able interpret correctly.)

Thus ATAT seems to work perfectly correct on the behalf I was hit by. I just need to implement a complete underived struct with impl AtatCmd with it's own as_bytes() and parse() functions for any command (featuring a response from the modem) I want to use. (Explicitly implementing as_bytes(&self) for sending instead of using #[at_cmd("... which I conveniently did up till now

Correct ? Thanks a lot for listening ! -Michael

mschnell1 commented 1 year ago

You are absolutely correct +1 Couldn't have written it better :)

Thanks ! Do you want me to write a draft of a documentation for the parts I do understand ? -Michael (needing to do such, anyway, to allow for cooperation on that project with my colleagues)

MathiasKoch commented 1 year ago

Absolutely! Any improvements to documentation is highly welcomed :+1:

mschnell1 commented 1 year ago

Sorry for my ignorance. This can't be an issue as it seems to be a basic functionality, but I don't seem to get a grip at it:

To do code that runs, when an URC is sent by the modem I fetched a subscriber by let subscriber = BUFFERS.urc_channel.subscribe(); I get a type UrcSubscription<'static, at::Urc> which seems to be fine. I understand that the correct way to go (when using tokio) is to spawn a thread by doing something like

   tokio::spawn(task_urc_1(subscriber));
   ....
async fn task_urc_1<T>(mut subscriber: T) -> !
where
    T: std::future::Future + Send + 'static,
{
...`

but here I get an error dyn PubSubBehavior<urc::Urc> cannot be shared between threads safely ... required by a bound introduced by this call A compiler suggestion says that the Send Trait is not implemented for UrcSubscription . and/ or atat::UrcSubscription How should I proceed ? -Michael

mschnell1 commented 1 year ago

For a test I switched the tasks and do let urc_message = urc_handle.await; in


#[tokio::main]
async fn main() {

and did


async fn task_no_urc<'a>(
    mut client: atat::asynch::Client<'a, atat_tokio::Tx, { atat_tokio::INGRESS_BUF_SIZE }>,
) -> ! {

for the client.send() stuff. This seems to work,. But the "subscribe" paradigm, seems to suggest, that the goal is to allow for multiple threads to concurrently wait for urc messages and handle them. That definitely is a great idea and should be available ! Thanks for listening ! -Michael

rmja commented 1 year ago

Year, I think the subscriber should be Send somehow. The pub/sub behavior relies on embassy-sync, and I do not know if it supports it.

mschnell1 commented 1 year ago

embessy-syc in fact shows "impl !Send" for DynSubscriber Thios is not the case with https://docs.rs/embassy-sync/0.2.0/embassy_sync/pubsub/subscriber/struct.Subscriber.html

I don't see how the really nice subscribe mechanism for dispatching the URC messages to multiple threads / cores should work without Send. I have no experience at all with embassy, and did test only with tokio up till now. Hence I don't know if embassy might not require Send... -Michael

mschnell1 commented 1 year ago

Did I inadvertently close this ?

mschnell1 commented 1 year ago

Closing this and creating an issue for the remaining problem(s)