ARMmbed / mbed-os-experimental-ble-services

Incubator of Bluetooth services
Apache License 2.0
7 stars 8 forks source link

[WIP] Add service class boilerplate generation tools #19

Closed AGlass0fMilk closed 3 years ago

AGlass0fMilk commented 3 years ago

This commit introduces an experimental boilerplate generator using the python library cog. The associated cog templates will product a basic .h and .cpp file for a service specified by the given .json file. A template service definition json file is also included.

Usage: cog -d D spec_file=example.json ExampleService.h ServiceTemplate.h cog -d D spec_file=example.json ExampleService.cpp ServiceTemplate.cpp

Example output has been attached as a zip. The zip contains an example .json service specification along with the generated ExampleService.h and ExampleService.cpp

ExampleService.zip

AGlass0fMilk commented 3 years ago

@pan- While it's not feasible to make this work for every situation, I think this can save a lot of copy-pasting and typing when doing new service classes.

We can even make a generator that takes the input .json and creates a properly-formatted issue template (with most of the fields filled out already!)

AGlass0fMilk commented 3 years ago

It would be nice to make a python module to encapsulate some of the more common formatting required. I didn't want to start making a whole installable python package at this point though. I'm not sure if cog would be able to pick up packages that aren't installed in the python environment.

pan- commented 3 years ago

I like the idea of boilerplate generation tools to create services. The json description of the service in your example is clear. I wonder if it can be extended to authorization callbacks and more importantly if it's possible to have a model where the service declaration is separated from the service logic. In such model, it would be possible to add new characteristics to the service and re generate the declaration without breaking the existing code.

pan- commented 3 years ago

I will provide a detailed review when I'm back from holiday; next year.

AGlass0fMilk commented 3 years ago

I will provide a detailed review when I'm back from holiday; next year.

Happy Holidays and Happy New Year! :champagne: :smile:

AGlass0fMilk commented 3 years ago

@pan- Bringing this back on your radar.

farrenv commented 3 years ago

This is really slick! I needed a '-' in front of the 'D'. I also need '-o' in front of ExampleService.x to create the output file. I didn't have the 'inflection' package and was able to pip install.

AGlass0fMilk commented 3 years ago

I like the idea of boilerplate generation tools to create services. The json description of the service in your example is clear. I wonder if it can be extended to authorization callbacks and more importantly if it's possible to have a model where the service declaration is separated from the service logic. In such model, it would be possible to add new characteristics to the service and re generate the declaration without breaking the existing code.

I'm thinking of ways to do this. My current idea follows the EventHandler paradigm found everywhere else in the BLE API.

I can add sections to the JSON specification file that allow one to specify an EventHandler API. The details will have to be worked out as I develop this (some caveats are not initially clear). Some ideas I have so far:

I also want to add fields that let you add documentation/comments to the generated service. This enables the tools to generate corresponding documentation (I'm planning to support LaTeX, which will allow nice PDFs to be generated).

My goal is to automate a lot of the work I do translating service specifications into code and customer-facing documentation.

I think we can separate the logic from the specification in this way, using EventHandlers. I will spend some time working on this.

AGlass0fMilk commented 3 years ago

Additionally, we can provide generic BaseEventHandlers for the auto-generated services. For example, if a service has some required logic on a given characteristic (eg: the DFUService has certain bits that are read-only in a bitfield characteristic, so must reject writes to these bits by default), that logic can be implemented in a BaseEventHandler. Out-of-the-box functionality would then be provided by either that BaseEventHandler or some SpecializedEventHandler that we provide.

For the DFUService, this would accomplish decoupling the BlockDevice API from the DFUService -- we can provide a BlockDeviceDFUServiceEventHandler that implements the BaseDFUServiceEventHandler using the BlockDevice API. If the user wants to implement some custom functionality they may subclass the BaseDFUServiceEventHandler and put their logic there.

Similarly, if a user wants to extend a service (eg: add an additional control characteristic to the DFUService) they can subclass the DFUService to do so. We will have to consider how we structure services to allow this kind of customization though...

AGlass0fMilk commented 3 years ago

Perhaps we can instead have this generate tool create an "Interface" class that implements the "boilerplate" constructor (initializing all the characteristics), class members (characteristics), and basic service API.

Then the implementation can subclass the interface. This prevents the generation code from getting unmanageably complex.

The above EventHandler scheme can then be used to further decouple application-specific logic from the BLE service itself.

pan- commented 3 years ago

I finally took the time to look at this work and it looks good! I have few observations:

I'm thinking of ways to do this. My current idea follows the EventHandler paradigm found everywhere else in the BLE API. I can add sections to the JSON specification file that allow one to specify an EventHandler API.

I think this could work, if the generated class has setter/getter and offer an event handler interface that forwards the authorization callback to the classes using it then it becomes easy to create a service from a generated one through composition. The boiler plate of the user facing service would just be the private inheritance of the EventHandler in the service declaration and registration of itself as the event handler. The service declaration class would not have to be changed.

AGlass0fMilk commented 3 years ago

Closing for now as this will require some additional discussions regarding the BLE application framework and service structure.

AGlass0fMilk commented 3 years ago
* We don't need to keep the characteristics or the value in the class once they have been registered. From a memory standpoint, I think it would be more efficient to instantiate the GattService and GattCharacteristics when they are registered. In the service generated we can keep the handle to the characteristics.

How would this work? We need to be able to get the characteristics handles, for example when filtering GattServer events.

Without dynamic memory allocation how would this work?

* I think it could be useful to have some getter and setters -even if they are just internal - to the characteristics value, that would avoid writing gattServer().read() and provide a more user friendly interface to work with.

I agree. I'd like to come up a with a common pattern for making new services and then this boilerplate generation tool can take care of generating a majority of that.

* In the JSON representation I think starting an `mbed` object instead of the `type` field would give us freedom for future extensions. For example, what if you want to link a characteristic with read or write callbacks, what if you want to customize the variable name, etc. That's not problem that have to be solved today but it would make things more readable. It can also be useful if one wants to tackle supports of other OS' like Android or IOs to generates clients or servers.

Can you give some example JSON? I'm not understanding what you mean.

I'm thinking of ways to do this. My current idea follows the EventHandler paradigm found everywhere else in the BLE API. I can add sections to the JSON specification file that allow one to specify an EventHandler API.

I think this could work, if the generated class has setter/getter and offer an event handler interface that forwards the authorization callback to the classes using it then it becomes easy to create a service from a generated one through composition. The boiler plate of the user facing service would just be the private inheritance of the EventHandler in the service declaration and registration of itself as the event handler. The service declaration class would not have to be changed.