felixwrt / sml-rs

Smart Message Language parser written in Rust
Other
11 stars 3 forks source link

add serialport example #14

Closed felixwrt closed 1 year ago

felixwrt commented 1 year ago

I don't like having to add an optional dependency to sml-rs just for one example, but here we go.

Alternatives:

I'm leaving this PR open for now while I let my thought sink in for a bit. Comments / opinions welcome.

(BTW: a similar issue will arise when implementing an async parser, which requires deps like tokio to build meaningful examples)

torfmaster commented 1 year ago

You don't need to add serial port as a dependency (which would be the wrong level of abstraction, I think). You can implement a streaming parser on top of an appropriate Stream, see https://github.com/torfmaster/hackdose/blob/main/sml-parser/examples/serial-stream.rs.

felixwrt commented 1 year ago

Right! I want sml-rs to be agnostic of a specific data source implementation (serialport, tokio-serial, embedded-hal, ...) and adding them as deps feels wrong.

On the other hand, I would like to include examples that show how to use sml-rs with all of these sources. Unfortunately, it's not possible to specify additional dependencies for examples. The workarounds are to put the dependencies in dev-dependencies or dependencies, non of which I like. I'll probably create separate example projects instead, either in this repo or in separate ones.

I took a look at your project yesterday and wondered whether you'd still be interested in using sml-rs. I wanted to support async for some time and it looks like your project could take advantage of that. I'm still figuring out how such an API should look like and which traits to base it on. I'd be very happy to collaborate on that.

torfmaster commented 1 year ago

I see two ways forward here

Regarding the usage of sml-rs: I'm pretty happy with my current implementation based on PEG, it's easy to adapt and robust (yet, missing a lot of features). I need to do changes frequently due to "interesting" implementations of the SML protocol in the wild and the implementation lets me do those changes in very little time (thanks to excellent tracing support by the peg crate). No offense: it's a philosophical question whether one wants to manually implement a parser or whether to use a parser generator but I would stick with latter for maintainability reasons (having very ittle time and no need for manual optimizations).

felixwrt commented 1 year ago

Alright, that's totally fine with me. I like the parser combinator approach I've chosen as it allows me to build simple parsers for low-level building blocks (like Type-Length-Fields) first and combine them to higher-level ones. I started building the parser with nom until I realized that SML is so easy to parse that I only used a tiny subset of nom. I then just replaced that with a couple of parsing functions and got rid of the dependency on nom.

Using a parser generator also looks like a good approach though. I've never really used parser generators, maybe I should check out peg some time. It'd be interesting to see how the two approaches compare regarding code / stack size and performance. I'll probably have a look at this some time.

Regarding "interesting implementations of SML": can you point me to these specialties? I'd like to support them in sml-rs too. Btw, do you know the libsml-testing repo? It contains data from many smart meter models and can be used to check a parser implementation. I'm using it in sml-rs's integtests and it discovered one or the other bug. If you have data from meters that aren't part of the repo yet, it'd be great to contribute them.

I just had another look at #5. We talked about the parser above, but what about the transport layer? I had a quick look at your transport implementation and think that you could benefit from using sml-rs's transport decoder. A couple of advantages:

It looks like using sml-rs's transport decoder in hackdose would be quite easy. I can prepare a draft PR so we can discuss in more detail if you like.

felixwrt commented 1 year ago

Update: I found a solution that I like. I've created a separate crate (sml-rs-serialport-example) for the serialport example and placed it in the examples/ folder. I also added the crate to the workspace in the top-level Cargo.toml.

Using this approach, I can use serialport as a dependency in the example without needing to add it to sml-rs in any way. The example is still discoverable as it's in the examples/ folder. By default, the example crate isn't build when using cargo build from the root folder. That's a nice thing, as serialport has dependencies on system libraries (e.g. libudev) and fails to compile when they're not found. Building / running the example is easy: either use cargo run -p sml-rs-serialport-example from any folder within the repo or cargo run from within examples/serialport.

I'm going to use the same approach in the future to add more project examples (e.g. embedded examples for the blue pill or esp32s).

I'm merging this PR, but feel free to add comments anyway.