Post date: Mar 19, 2016 12:22:15 AM
Having cross-compiled ngspice to run on Android, the next task was to be able to call this binary from an app.
The first task to to provide a working directory for the input and output files to SPICE. This was achieved with Context.getDir("run", Context.MODE_PRIVATE) which creates (if necessary) and gives us the path of an app_run directory on the local data storage of the phone. We can then write the netlist to simulate to this. We use the Apahce Commons IO library (org.apache.commons.io.FileUtils) for convenience.
Next we have to provide the ngspice binary. The method we chose to implement is to include the already-built ELF file as a raw resource in the app, and copy this into a new file 'ngspice' the run directory and execute it there. This copy loop has to be written out manually since openRawResource() gives a stream rather than a file (presumably the stream is backed by a ZIP decoder rather than a file). We can then set execute permission on the file and run it. Theoretically Android does support a better managed solution (using its JNI library handling) but this assumes you build the C code in gradle, which we are not doing for reasons already discussed.
The actual execution of the SPICE process is carried out using a ProcessBuilder. The significant advantage of this over Runtime.Exec is that we can set environment variables for the call. Here we use this to set SPICE_ASCIIRAWFILE=1 so that the results will be easier to parse. Naturally the process will be longer lived than a few milliseconds, thus an AsyncTask called ProcessAsyncTask was written to run the process in the background.
Specificially the simulations are run as
/.../app_run/ngspice -b -r rawfile netlist.sp
ProcessBuilder expects the user to handle the standard output and error of the process. From Java 1.7 libraries or higher it has a redirectOutput(File) call to redirect the output to a file, but Android defaults to Java 1.6. Thus we instead create two further background threads to spin doing blocking copies of the streams to files. A better solution would be to require Java 1.7 libraries but this would raise the minimum Android version to Lollipop.
To separate the logic of interfacing with SPICE from the UI elements, a class SpiceRunner was created for the application logic. Due to the AsyncTask usage, the SpiceRunner class both receives callbacks form the AsyncTask, and then itsself calls back to the UI activity. The AsyncTask calls back success or failure, and the SpiceRunner either calls back the UI thread with a status update, or, later on, starts further AsyncTasks (for future result processing).
Taking advantage of this seperation, a first a trivial activity with just a button and textboxes for stdout and stderr was written, knowing it would be easy to use SpiceRunner from another activity later.
Having done this, we could successfully run a SPICE simulation from an app running on Android Virtual Device but simulation then fails on a real phone (Moto E). Investigating, it turns out that a call to tmpfile() is failing. tmpfile() is a C standard library call that opens a file with a random file name under /tmp, so a reasonably anonymous temporary. However Android doesn't have one standard file hierarchy itself, and definitely doesn't follow the Linux standard. Thus on some phones /tmp either doesn't exist or is root-only. Thus we need to make the temporary under our own app data folder. Since ngspice only uses one temporary file at a time, it was sufficient just to replace the tmpfile call with a fopen("tmpfile").
Having done this SPICE simulations could now be run directly on the phone.
The next task is to load the rawfile that results from the simulation.