Close

The StarkBrain File

A project log for Project Stark Framework

This is a YET ANOTHER attempt at an "Iron Man" Jarvis-like system

robweberrobweber 06/24/2016 at 15:300 Comments

Continuing with the idea of more of a "deep dive" into the structure, this is an overview of the backend storage of the framework. Dubbed the StarkBrain, this is how the system attempts to store information in an abstract way so it can be easily recalled and manipulated by the different modules.

In the early days of the framework the idea of a "brain" for the system became evident very quickly. There are things like settings to consider, conversation history, data around jobs, etc, etc. The easiest way to solve this in the beginning was to write some flat files, but as the system got more complex a better way was needed.

Currently the Stark Framework utilizes two data stores. The first is for the History object, which stores all event and command history. This is a not-very-exciting SQLite database. It has a defined structure, is easily portable, and quick to query. Usually only information about the previous event, or events within the last day are needed anyway.

The more complex, and fun, is the data object known as the "StarkBrain". This is a Java object that utilizes Redis as a backend, with JSON as a data storage mechanism. The abstraction here is done via the excellent jedis and json simple Java packages.

This structure allows modules to store their own unique lists, maps, and data objects without having to know anything about the underlying structure. Each simply has to implement the Java interfaces to dump the JSON information and read it back in again. In the case of strings and primitives, those can be stored directly. Here is an example from the iCal Calendar module for the "AddCalendar" method.

//get all the calendars for this user
Map<String,UserCalendar> allCalendars = brain.getMap("calendars", UserCalendar.class);
UserCalendar calendar = allCalendars.get(user.getUsername());

//add a new calendar based on the iCal URL
calendar.addCalendar(args.getArgString("name"), args.getArgString("url"));

//initiate an update
calendar.refreshCalendar();
allCalendars.put(calendar.getUser(), calendar);

//save everything
brain.store("calendars", allCalendars);

Notice the calls to the brain.getMap() and brain.store() functions. Every module is issued it's own namespace within the Redis db by using its unique java class name. This namespace is accessed via the "BrainFile" object. This object can store a simple string, a java List, java Map, or a unique object type that implements the JSONAware interface. In this example the UserCalendar class implements this interface so the brain object is asked to load the JSON object for the key "calendars" and create a map, converting each JSON object to a UserCalendar object. The brain.store() method similarly will take this object and call UserCalendar.toJSON() to save these objects as a JSON string back in the Redis data structure.

In trying to emulate the brain theme a little more an additional function was added - the ability to forget. Using the Redis EXPIRE feature a module can purposely set a value to be forgotten by the brain system. This is useful for values that will only maintain there relevance for a particular time period. I've often used it in situations where you only want to know about an event once a day, just saving a key value for the event with an expiration at midnight. If the key still exists, don't resend the event.

Another interesting feature is that modules can load each other's BrainFile information. Since the namespaces are created based on the java class name, other modules can load these namespaces. An application of this can be seen in the Network module. The Wake On Lan feature can take a MAC address, or optionally the name of a specific Kodi computer name. This is a shortcut method to knowing the MAC since the Kodi module already records the MAC address of each Kodi machine that registers with it.

//load the kodi brain file
BrainFile kodiBrain = StarkBrain.getInstance(KODI_CLASS_NAME);
Map<String,KodiHost> hosts = kodiBrain.getMap("hosts", XbmcHost.class);
			
//get the host specified by the name given
KodiHost host = hosts.get(args.getArgString("instance"));
			
if(host != null)
{
	WakeOnLan wol = new WakeOnLan();
				
	if(wol.wake(options.getArgString("broadcast"),host.getMACAddress()))
	{
		c.setResponseType(Stark.ResponseType.IN_PROGRESS);
		c.addResponse(Stark.Response.IN_PROGRESS_SUCCESS_EVENT,"Kodi.Online");
		c.addResponse(Stark.Response.MESSAGE, strings.getRandomString("turning_on_computer",args.getArgString("instance")));
	}
	else
	{
		c.setResponseType(Stark.ResponseType.ERROR);
		c.addResponse(Stark.Response.MESSAGE, strings.getString(4));
	}
}
else
{
        //can't find it
	c.setResponseType(Stark.ResponseType.ERROR);
	c.addResponse(Stark.Response.MESSAGE, strings.getString(2, args.getArgString("instance")));
}

In the top line you can see module loading the Kodi BrainFile, and then using that to get information it has previously stored. Not being shown here, for brevity, is that you should also check that the module is even loaded before attempting to access information. This can be done easily with a call to Stark.getModuleInfo().isModuleEnabled().

The goal here was to create a non structured way of storing data that allowed modules to persist information easily between events. So far it seems to be doing the job. By utilizing Redis the brain information can also be easily backed up and made accesible over a network using existing redis libraries.

Discussions