Package: mkernel
mkernelThe mkernel project (pronounced em-kernel) provides the extensible kernel used by the meerk40t project for any other projects. While developed with that in mind it was developed generically, and as such can be used for any project which may require any of the robust environmental features provided by the Kernel.KernelThe Kernel class provides a suite of functionality. It serves as a central hub of communication between different plugins within the system.Features include:Lifecycle based ecosystem of functionality.Job scheduler to provide repeated and time delineated function calls.OS independent context-path dependent persistent settings.Tree/PathContextto provide path-dependent views into the kernel.Internal console command system with easy registration of context based commands (Similar to Click).Command context pipelines for user executable modular data manipulation (Similar to Vpype).Centralized lookup for registered data across the program ecosystem.service.providersregister a standard method for starting a particular service (if needed)Serviceprovides direct reference-attributes to all contexts.Serviceprovides methodology for swapping lookup data between different instances for the same service namespace.lookup_listenerprovide internal listening mechanisms for lookup changes.signal()provide efficient centralized communication for specifically flagged data.signal_listenerto register a function to be called by the signals.Channelto provide realtime lossless stream of data for debugging or utility that can be accessed by the user.Modulesto provide reusable, registered classes, with properly bound lifecycles.plugin-based architecture to provide highly configurable plugin registration.lifecycle-aware plugins forModulesandServices.Manage lifecycle aware threads.Translation information and functionality.LifecycleThere are three different lifecycle aware types within the ecosystem:Modules,Services, andKernel.Plugins are called for all events in an objects lifecycle (see methodology). The lifecycles events are called as specific functions on the lifecycle-aware objects themselves. AServicewill be notified ofservice_attachby calling (if it exists) adef service_attach()function.DelegatesLifecycle aware objects can also calladd_module_delegate()for Modules oradd_service_delegate()for Services. This will then not only call the lifecycle events on the primary object but also on all delegates of that object. Delegates will also have their@signal_listener()and@lookup_listener()on their functions automatically registered.Plugin ConfigurationThe lifecycle events include a few pre-processing and configuration calls.plugins: All plugins are first polled for plugins. This should, if it returns anything, return a set of other plugins.service: Plugins are queried forservicewhich should respond with theproviderthey are targeting. This service will be attached to the lifecycle of any and all services created from that provider.module: Plugins are queried formodulewhich should respond with the path of the module they are targeting. This plugin will be attached to instances of that module that are opened.invalidate: Plugins are queried forinvalidatewhich should returnTrueif this plugin is invalid and should be removed from the list of plugins.Kernel LifecycleBy default, plugins are expected to bekernel-pluginsand react to the lifecycle events of the kernel.
These are in order:preregisterregister: Mostregister()calls and establishment of different parts of the ecosystem should be done here.configure: Everything was registered but instances of other lifecycle objects are not started.boot: Most lifecycle objects are started.postbootprestartstartpoststartreadyfinishedpremainmainloop: Theconsoleor various gui mainloops will be executed here, they should capture the thread.postmain: All the holds of the mainloop have been released.preshutdown: Everything should be unhooked and ready to be destroyed.shutdown: The kernel is terminating. You cannot count on anything still existing. Do housekeeping.All registered plugins calldef plugin(kernel, lifecycle)this is called with the kernel object and the current lifecycle. This is done for each lifecycle event. Plugins are required to use these to register and execute themselves at the appropriate times within the overall program's lifecycle. The primary reason for many of these events are that sometimes code execution requires that other registrations and code has executed before the code being provided. If they are registered at the correct lifecycle event, that guarantee is met. For example, if you need to execute a console command this should be done afterregistersince all console commands should be registered during that lifecycle event.The kernel lifecycle is started by the__call__()namelykernel(). This will by default process the entire life cycle frompreregistertoshutdownthe expectation is that something aroundmainlooplike a gui or console will catch the thread and hold it there until a customquitcommand is executed. However, there is also akernel(partial=True)which will only execute part of the lifecycle and exit out of the function with a fully booted ecosystem. You are expected to issue akernel()command to complete the lifecycle or allow some method to callquitinternally to perform this, a GUI would be expected to capture and use the thread at themainloopand console would do this accordingly. It depends on the current usecase.defplugin(kernel,lifecycle):iflifecycle=='register':@kernel.console_command('example',help="Says Hello World.")defexample_cmd(command,channel,_,args=tuple(),**kwargs):channel(_('Hello World'))In this example, we use thekernel.console_commandto register a command called"example"which calls the channel with "Hello World" during theregisterlifecycle.
See:examples/hello_world.pyModule LifecycleModules are only expected to be started up and closed down. Their lifecycles are:module_openmodule_closshutdownSchedulerThe scheduler is a thread which providesJobinstances these can occur a number of times over a given duration. A scheduled job will skip run cycles if the job takes longer than the interval to complete. Refreshing guis and signals are processed through scheduled jobs.The kernel scheduler is allowed for jobs to be scheduled. It launches a thread in the kernel which iterates through the different scheduled jobs, and running them when required. This thread is also used as during shutdown to terminate everything in a safe manner.ThreadsThreads are registered in the kernel and executed normally. These are tracked and viewed with thethreadchannel for diagnostic purposes. And are required to complete for kernel shutdown. Starting a thread requires using thethreadedcommand on the kernel or a context.JobsJobs are either called within a context for.add_job()or a module can extend the Job and callschedule()andunschedule().TimersTimers are user set internal commands that execute repeatedly. These can be executed by the user in the console. This is an internal kernel command.help timer
     	timer.* <times:str> <duration:float>
     	(None) -> timer.* -> (None)
     	Argument: str 'times':
     		Number of times this timer should execute.
     	Argument: float 'duration':
     		How long in seconds between/before should this be run.
     	Option: bool ('--off', '-o'):
     		Turn this timer off
     	Option: bool ('--gui', '-g'):
     		Run this timer in the gui-threadContextsContexts are the usual method of accessing data within the kernel.kernel.get_context(<path>)will give a context with that specific path set. This path is used as a namespace to allow multiple similar aspects. For example, if you had acameraplugin, it could register incamera1,camera2,camera3for identical but independent settings in the different namespaces.Contexts also allow for simplified and shorthand commands compared to kernel.context("help")will call the help command in the console.Signals, always provideoriginas their first parameter. This is the context which issued the original signal.Persistent Settings.TheSettingsuses python ConfigParser to write our persistent settings to disk. This is used by the Kernel to save any settings data on a context to disk duringshutdownThe individual settings are usually initialized by callingq = context.settings(<type>, <attr>, <default). If the setting was found in persistent settings then the persistent value will be used, elsecontext.<attr> = defaultis set.Persistent settings require a context to save. In most caseskernel.root, would serve as a reasonable context for saving settings that do not need to specifically subclassed.ModulesModules allow many copies of the same class. These are opened at the context and stored in the.openeddictionary at a context. These are given a name which can be dynamically assigned. They are not expected to change the context at which they are opened.Modules are opened classes. They should be registered in themodule/<module-name>path in the kernel. These are opened and attached to contexts. These are opened using theopen()function on contexts and kernels. If the module, with the same namespace, is already opened, the opened module is returned and any initialization parameters are called on therestore()function on the given module (if it exists).ChannelsChannels are dataflows within the kernel. For example, if a plugin is communicating over usb, it can provide a log of this communication by opening a channel. Channels are.watch()and.unwatch().The channels are an aspect of the Kernel. These allow channels to be opened and watched. These will convey every message sent to any watchers observing that channel. There does not need to be an open channel for that channel to be watched, or a watcher for that channel to be opened. Channels provide streams of information while being agnostic as to where the information will end up.SignalsSignals are a lossy update-type dataflow within the kernel. Unlike Channels, if a signal is called many times very rapidly it will result in a single trigger of the signal for thesignal_listeners. You are not guaranteed to see every signal call, you are guaranteed to see at least one signal with the latest update. The signals will always be called in the kernels' main thread. In GUI applications this would typically be the GUI thread, thereby allowing threadsafe graphics updating.The signaler schedules itself within the Scheduler, and provides functionality forlisten(),unlisten()andsignal(). A@signal_listener()attached to a life-cycle object is life-cycle aware and willlisten()whenservice_attachormodule_openand willunlisten()whenservice_detachormodule_close. So that a Service which is not currently active will, if flagged with a@signal_listeneronly receive signals while active. This allows non-duplicating signals to send, so other instances within the kernel ecosystem know they should update, if they listened for a specific signal. The signaler does not force every message to be delivered, only the last message.When a listener is attached, it will get the last message for that signal if it exists. Regardless when that last signal was issued. The reason for this is that if they are used for GUI fields, they will always be given the last signal to allow fields that might not have been created when the signal was originally issued to have the most recent data.