I recently completed a first version of a parent child setup of apps with one child app per type of house climate actuator (fans, underfloor heating, radiators etc) and a parent app providing meteorology data and instructions to all child apps and also acting as an information master. I became aware of that this is a bit tricky, at least for me. I realised I needed four variable flows though the apps. One first initial value population of variables, a second to act on events, a third to transfer common variables from the parent and a fourth to transfer variable values from any child apps that sits on master information to the parent app, for distribution to the child apps. This got me into all sorts of issues like timing issues, race conditions, involuntary change of object types and so on and so on. I got really good advice from this community on some of those issues, whit out which I had not been able to complete the task. I also started to cheat on the principals and send data directly between child apps out of convenience. In the end this led to the need for a rather cumbersome dispatch system to introduce exact controlling of all sending of information in order to be able to guarantee that data was accurate at all points of interaction, sent as planned at the right moment, that the child apps was ready and initialized to receive the data as planned and so on. To manage this was if not more challenging so at least as challenging as the actual functionality the apps are actually doing and are intended to do. I am sure there are more clever ways to do this than the meticulous way I did it in. One way to go which I contemplated was to rely on hub variables for as many parameters as possible. In the end I didn't feel I could 'own' the updates of these to know which value each variable had at any given time. But maybe that would have been as reliable and functional in the end? Perhaps even better and definitely easier to do.
Have you done any parent child setup with apps where you transfer data between the apps. How did you do it, what flows of data did you accommodate for and what was your experience with all transfer related issues?
This might be easier to answer if you share what steps you have tried so far (specifically) and what did or didn't work.
But in general, if you just need to share data among child apps, the parent seems like a good place to store that information. If you have concerns related to concurrency, including race conditions, using the singleThreaded: true parameter in your (parent) app definition is probably the simplest approach, though there are other approaches, depending on how you want to store the data and whether it needs to be persisted across reboots, etc. or is more of a cache.
You can then define a method in your parent app code to set and get these values that you can call from the child. With singleThreaded: true, the state object is probably the best place to store these (or atomicState without that), though again, it depends on what your needs are. In any case, the methods would just read and set these values -- however you're doing that part.
I went down this rabbit hole a couple of times with parent/child relationships. Both in Parent app to Child app, and then Parent app to Child device. It seems easy to think you can just call stuff back and forth and across devices, but it can become a spaghetti mess very quickly depend on how many places you go.
My experiments were related to both the InfluxDB @dennypage maintains and then my own Govee Integration, and Humidity Manager apps.
The experiment I did with InfluxDB a while back was to allow separate child apps for certain functions and then they all reported post data back to the parent main app. It got interesting because I went 3 levels deep with the child apps. It worked and was a interesting setup with multiple apps for each function and fantastic customizability, but ultimately I abandoned it because what @dennypage was doing was just so much better and easier as he kept improving the single large app.
The Govee Integration used the Parent Child relationships between the parent app and child devices. This later expanded to two levels of child devices. The initial use was to allow a central point to hold a large amount of info and then let the child devices retrieve the needed data easily. Basically Large map values, but ultimately that method was less the optimal. Performance wasn't the best and allot of code was used up. There were also noticeable delays when cross between parent and child. At this point most of that stuff has been removed except for a few small exceptions.
In most cases parent child relationships for apps are simply a organizational like the Humidity Manager I have. It is just a way of organizing the multiple instances of the same app. For instance I have one humidity manager app for my study and another for my Master Bedroom. Those installed apps sit under a main Humidity manager app just as a container.
In my experience I have tried to keep the parent child relationships fairly simple and direct in purpose. As an example I had a routine in my parent app in Govee that validated a value and simply passed a parm to the children devices to update their value if it was different then before. I also have a routine in the Main parent device that response to a incoming MQTT message and then parses it and based on a value sends the update to the appropriate child devices.
The point there though is that stuff that was crossing from parent to child or vice versa was very directed. Try to do the KISS method when crossing between levels to prevent problems from occurring.
Some of my drivers do some "back and forth" between parent and child devices (I do not have any apps, so this is for people interested otherwise). For the parent sending events to the child I have things like "PostEventToChild" which (in the end) calls some variation of:
That tells it which child device, and calls the ProcessEvent there (just a fancy way of me checking stuff before doing a sendEvent command). The parent actually has the exact same ProcessEvent for itself. This lets me get events (I have a similar one for States) TO a child device.
To get data from the child I have it call something like:
The child's "ReturnState" gets the State Variable requested and returns it to the parent device. This way if the parent needs some piece of information from the child, I can just call for it and do not need to keep an absurd number of variables in the parent device (any more than I already do at least).
Unless something changed in the last year singleThreaded can be challenging on parent/child apps. I tried using it with one of my community apps and it caused problems. I PMed @gopher.ny about it and he said:
The alternative of atomicState that I mentioned might be a good alternative in such cases. (Though without any information on what this data actually is, specifics are hard to suggest--but app/driver state works well for most general persistence needs.)
It seems like this is avoidable, however, if the method you call from the child on the parent does not itself (in the parent) call a method on the child -- which for a simple getter/setter pair of methods, as is the suggestion here, seems easy enough to avoid.
I may have posted this in the wrong category. Sorry if that is the case, please feel free to move it if that is appropriate. There are many many situations that occurred over the months it took me to develop the apps, and I didn't document them and repressed them as I wolved them . Now it works, so there is no immediate problem to solve. The intention with this post was more of a general coffee machine discussion about the complexities of concurrency in general and transferring data in a parent child setup in particular.
As I have mentioned before I think, I did some Java programming in an Oracle ERP environment many many years ago. In that type of environment all things are stable all of the time and nothing changes unless you decide it should change. In this environment though, programming is more like cocking on a ship in a storm where pots suddenly transform into pans and then the food disappears for no apparent reason.
With that said, thanks, I didn't know about the singlethread feature. I read a bit about it and it is useful. As I did not mention it more specifically it would not been possible to know, but the issues I have encountered have also been more granular I think. A large part of them occurred around initialization when single thread had been very helpful, but after initialization I would like the apps to not be single threaded. The majority of the issues related to initialization has been timing issues. A child app where the general functionality of the app is dependent on certain common data having been distributed and available at the time of execution. In the end I choose to create a controlled sequential initialization where the parent app after having been initialized, initializes each child app, then sends the data and then, not before, lets the child app start to do its thing. I controlled this using flags. Then the next child app and so on. Cumbersome but works. This solved the timing issues. This approach also sorted all race condition errors during initialization and made it easier to resolve the data corruption issues that sometimes also occurred. At my proficiency level my conclusion is that all initialization and initial load of data has to be sequential, otherwise it won’t work.
Interesting @mavrrick58 , thanks for sharing. I decided to use the parent child setup mainly to be able to develop one app at a time and as a way to organizing the code. For all seven apps it is around 10.000 lines of code and I find it cumbersome enough to navigate in each app as it is. I am sure a more profificient would be able to achieve the same result in far fewer lines of code, but thats were I am at the moment, and it works. (With that said, I have come to realize that he scope the apps try to solve is virtually impossible to solve, so maybee the amount of code has some motivation)
Interesting to read about your setup with a child app for each instance. In this, my frirst atempt, I went for one app for each device type, i.e. one app for all underfloor heaters, one for all radiators and so on. The main reason for that being to limit the level of child apps to one. With that said, all devices still needs to be handled individually so they might as well be a child app of their own each, as you have done.
Thanks @ritchierich , that is useful to know I added instructions to handle that situation, but for other reasons, unaware of this situation. Lucky I did then.