Saturday, November 19, 2022

 At Home CADR Testing for Air Cleaner Devices

Introduction


 

These instructions are for performing an at-home CADR test in a small chamber such as a bathroom or a portable greenhouse. This procedure should give pretty precise results, with an error estimation.

A simpler but less precise procedure is described in this dylos video:


Their spreadsheet can be used, or Portland State's ACE-IT spreadsheet https://www.pdx.edu/healthy-buildings/ace-it

I have yet to do a thorough evaluation of the difference between this method and those spreadsheet methods, but an initial evaluation suggested they can be off by a bit. That is fine for some use cases and in those cases those methods are appropriate.

 Supplies

  • Aqueous salt solution
    • Purchase a premade, sterile 7% salt solution. This is preferable, but expensive.
      OR
    • Get deionized water (distilled is acceptable but deionized is better) & kosher salt (NOT common table salt which is iodized and does not dissolve well). Common sizes of these items, 1 Gallon and 48 Oz, are plenty and will last for many, many experiments. -- if you go this way you'll need a kitchen scale to measure the salt by weight.
  • Mesh nebulizer
    • The UC Davis paper uses a Wellue, but I could not find one. I have this one (link), it may disappear from amazon as tends to happen. A key feature for me is that it can be USB powered from a wall wart.
  • Sensirion SPS30 with Development Kit (link)
    • The development kit is an SPS30 + a $40 USB cable, so some might hesitate to get it. I understand that perspective. But that $40 USB cable enables the use of Sensirion's ControlCenter software (link) so not only does it remove the task of wiring up the SPS30 to something, it also removes the task of writing software to record the data. That's good value, and I recommend it. If instead you feel inclined to wire one up and write some software, have at it.
    • SPS30 records particle sizes from 0.3 - 10 microns, see the datasheet.
  • A computer to record the data.
  • A space to run the experiment that is within the recommended temperature and humidity range of the SPS30. From the SPS30 datasheet, "The sensor shows best performance when operated within recommended normal temperature and humidity range of 10 to 40 °C and 20 to 80 % RH, respectively." I try to keep to around 70 °F and 40 % RH.
  • A small table to put the particulate sensor on.
  • A portable greenhouse
    • The one I use (59" x 59" x 72") seems to be discontinued but a similar one is available (link).
  • A mixing fan
    • I use a small HT-800 series Honeywell. Any similar small table fan that can be angled upwards will do.
  • Some tape

 Outline of Experimental Procedure

Firstly, this technique builds off of the excellent paper "Characterizing the performance of a do-it-yourself (DIY) box fan air filter" by Rachael Dal Porto, Monet N. Kunz, Theresa Pistochini, Richard L. Corsi & Christopher D. Cappa which can be found here. See the supplemental section of the paper for a description of their technique. Roughly summarized, it is:

  1. Generate particulate matter in a room.
  2. Turn on an air cleaner device to remove that particulate matter.
  3. Observe the rate of decline of particulate matter using a particulate matter sensor, this allows calculation of the Clean Air Delivery Rate (CADR) of the air cleaner device.

Of course, there is more to it than that. A couple of really important things to consider:

  • Particle decay due to the natural ventilation and deposition that occurs in the space. In any space there is a certain amount of air exchange with the outside -- this is the ventilation component. It could be due to HVAC, an open window, or the unsealed bottom of a garden tent. Deposition is the process of particle matter floating around until it hits stuff in the room and settles on it. Particle decay due to ventilation and deposition can vary quite a lot depending on the environment chosen for the test and environmental conditions like temperature, humidity, etc. And it can be significantly large, so it has to be subtracted from the decay caused by the air cleaner in order to get an accurate measurement of the air cleaner.
  • The well-mixed assumption. The test is built on the assumption the particulate matter is well-mixed within the space. To achieve this, an extra fan is placed in the environment and pointed towards the wall.
  • Background particle concentration in the space. If the space has a significant background particle concentration (the particle concentration before the experiment starts) this makes the math that needs to be done... annoying. This can be dealt with by raising particle concentrations for the test to levels much higher than background or by running air cleaner device(s) prior to the experiment to reduce the background concentration. Once one of those things is done, background concentration is assumed to be zero.

Accounting for ventilation & deposition as well as making sure the air is well-mixed, the experiment becomes:

  1. Select a space for the test, hereafter referred to as the test chamber.
  2. Ensure background particulate levels are low enough (preferably zero) by selecting a test chamber with low particulate or running air cleaner(s) prior to the experiment.
  3. Turn on the mixing fan. It should be placed near a corner of the chamber, pointed at the corner.
  4. Begin recording particulate matter levels using the particulate matter sensor. It should not be up against the wall or immediately next to the air cleaner device.
  5. Generate particulate matter in the test chamber.
  6. Allow the particulate matter to decay so that ventilation and deposition in the chamber can be measured.
  7. Save the recorded particulate matter levels for the ventilation and deposition part of the experiment.
  8. Reset the particulate matter in the space to an acceptable background level (as close to zero as possible).
  9. Begin recording particulate matter levels using the particulate matter sensor. It should not be up against the wall or immediately next to the air cleaner device.
  10. Generate particulate matter in the test chamber.
  11. Turn on the air cleaner device. It should be placed near the center of the chamber.
  12. Allow particulate matter to decay so that effect of the air cleaner device can be measured.
  13. Save the recorded particulate matter levels for the air cleaner device part of the experiment.

That's the basic outline of running an experimental trial. Next, I will walk through the particulars and practicalities of performing the experiment.

A note on effects of the small chamber size:

Using the smaller chamber (UC Davis and AHAM test procedures use larger sized rooms) may overstate the impact of ventilation and deposition due to the small space. This may be exacerbated by the high mixing fan speed I have selected. The ventilation and deposition added by turning on the air cleaner device's fan(s) may be understated because the effect of the high mixing fan speed dominates. Both of these factors mean that the reported CADR in a small chamber may be an understatement when compared to testing the devices in a larger chamber. It is unknown how large this effect is, but given comparisons to AHAM tested devices that showed similar results, it is not expected to be too large.

Setting up the Greenhouse

Stand up the greenhouse. Tape the flaps to the floor to reduce how much air escapes out of the bottom, leaving room for power cords to run under the walls.
 

The power cords for the mixing fan and the air cleaning device are run under walls of the greenhouse so they can be turned on and off without disturbing the chamber.
 

Setup the Mixing Fan

Place the mixing fan in the corner of the greenhouse and run the power cord under the wall of the greenhouse. If you're using a small room, the mixing fan can simply be plugged in in the room since it can remain on for the duration of the test. I set my fan to the highest speed.
 

Setup SPS30

Put the SPS30 on a small table inside the chamber, then run the USB cable under the wall of the greenhouse. If you're using a small room like a bathroom you'll want to find some way to run it under the door.

My SPS30 is wrapped in several layers of aluminum foil as shielding because I suspect it may be receive some electromagnetic interference.

Before settling on the SPS30 I tested out the Plantower PMS5003 and the SDS011. I found that the results from those sensors were more unreliable than from the SPS30. Plus, the SPS30 has the ControlCenter software, which makes it much easier to get going.

Setup Sensirion ControlCenter

Get Sensirion ControlCenter from https://sensirion.com/products/sensor-evaluation/control-center/ and install it. It's available for Windows, Mac, and Ubuntu.

Then, plug the USB into the computer and start ControlCenter. The SPS30 shows up in the top left.

To start recording particulate samples (default rate is once per second, which is good) click 'Start'. We are interested in the PM2.5 Mass Concentration range as this will give a CADR that approximates performance for respiratory aerosols.


 

ControlCenter shows particle mass concentration, number concentration, and particle composition which is a live view of how the particles are binned.

To stop recording, click 'Stop'.

To get the data file that has just been recorded, click 'File' > 'Open Data Log Folder'

  

Mix Salt Solution

Using a kitchen scale measure out 30 grams of salt and 300 grams of water. The density of water is 1 g/mL so this is 300 mL of water. I am assuming you do not have any laboratory glassware, so this is a good way to way to get a precise amount of water.

Vigorously mix the salt and water together until the salt is no longer visible. I use a stirring plate and an erlenmeyer flask, but any capped container will do and salt will mix with water just by shaking or stirring.

Now you have 300 mL of 100 g/L salt solution.

Setup the Nebulizer

Positioning the Nebulizer

I place the nebulizer on the outside of the chamber, near the floor, poking through a small gap in the zipper.



I've got it propped on some things so it is level with the zipper. I get it level because if it's pointed up, it tends to think it's out of solution and shut itself off.

I plug the nebulizer into a USB wall wart for power.

Adding Salt Solution to the Nebulizer Cup

Fill the nebulizer's cup with the salt solution, taking care not to spill. It is best to fully remove the cup from the nebulizer and fill it a safe distance from the nebulizer. Salt water is corrosive and spilling it on the nebulizer can cause corrosion that will kill the nebulizer (I have done this). Wipe away any excess salt solution prior to putting the nebulizer cup back on the nebulizer.

Further, I recommend removing the nebulizer cup when going in and out of the greenhouse so it doesn't get knocked over and spilled, which could lead to corrosion of the nebulizer.

Cleaning the Nebulizer

After a number of experiments there will be salt buildup and the nebulizer may quickly shut off after being turned on. The manual includes thorough cleaning procedures. Most of the time it is sufficient to run the outside of the cup and the nozzle attachment under the sink.

Running the Experiment

Ventilation and Deposition Trial

  1. Make sure the SPS30 is connected to the computer.
  2. Place an air cleaner device in the center of the chamber, running the power cable under the wall (or under the door if using a small room). Deposition can change drastically based on the shape and materials of the air cleaner device.
  3. Turn on the mixing fan at high speed.
  4. Close the chamber, with the nebulizer poking in as shown in Step 7.
  5. Click 'Start' in ControlCenter to begin data collection.
  6. If pm2.5 is more than 10, run the air cleaning device to drop it under 10.
  7. Turn on the nebulizer
  8. Monitor the mass pm2.5 level in ControlCenter, stop the nebulizer when pm2.5 is greater than or equal to 800. It should rise to > 1000 before stopping.
  9. Allow the pm2.5 level to decay to under 100, then turn on the air cleaner device until pm2.5 is under 10 again.

Get the data file and name it something to do with the device being tested, with 'vd' in the name for ventilation and deposition.

Air Cleaner Trial(s)

  1. It is expected that this immediately follows the VD trial or another air cleaner trial so the mixing fan is still on, nebulizer still has salt solution in it, SPS30 is connected, etc.
  2. Click 'Start' in ControlCenter to begin data collection.
  3. If pm2.5 is more than 10, run the air cleaning device to drop it under 10.
  4. Turn on the nebulizer
  5. Monitor the mass pm2.5 level in ControlCenter, stop the nebulizer when pm2.5 is greater than or equal to 900. It should rise to > 1250 before stopping.
  6. Allow the pm2.5 level to decay to 1250, then turn on the air cleaner device. The analysis interval is [1000, 100], starting at 1250 gives time for the device to power up and get to full speed before the 1000 mark is hit.
  7. Allow the pm2.5 level to decay to under 10, then turn off the air cleaner device.

DO NOT open the chamber during this trial or the ventilation and deposition trial. This may drastically change the result, as it effectively adds a ton of ventilation to the experiment.

To reduce error, run at least three trials. For higher CADR devices (300+ cfm) it is expected that error will be larger in this setup. The error can be reduced by running additional trials.

The ventilation and deposition experiment should definitely be re-run if environmental conditions such as temperature and relative humidity have changed, if the air cleaner device under test is changed, or if anything has moved inside the test chamber. It is good practice to re-run it for each complete set of trials.

Dealing with Air Cleaners that Don't Turn on When Plugged In

If it's a smart device, you may be able to turn it on with an app.

If it's not, try opening unzipping the chamber a small amount, one or two inches, and stick a broomstick in to poke the button you need to poke. A hole this small won't alter ventilation and deposition significantly.

Data Analysis

The supplementary material of the UC Davis paper goes into this, but here is an overview.

Particle decay follows an exponential decay curve. This equation is:

Concentration_t = Concentration_bgd + Concentration_t0 * e^(-ACH * t / 3600)

Where:
  Concentration_t = Concentration at time t
  Concentration_bgd = Background particle concentration (we assume this is 0)
  Concentration_t0 = Concentration at the beginning of the decay interval
  ACH = Air changes per hour
  t = time (in seconds)

(divide by 3600 converts from seconds to hours to put ACH in terms of hours)


The data collected allows fitting a curve to find the ACH term. The ACH term can then be used along with the room volume to determine CADR.

CADR = V_r * (ACH_total - ACH_vd) / 60

Where:
  CADR = Clean air delivery rate, expressed in cubic feet per minute
  V_r = Volume of room, in cubic feet
  ACH_total = ACH from trial where air cleaner device is on
  ACH_vd = ACH from trial measuring ventilation and deposition

(divide by 60 converts hours from ACH term to minutes for cubic feet per minute)


There's two tasks:

  1. Fit the decay curve for the ventilation and deposition trial to find ACH_vd
  2. Fit the decay curve for an air cleaner trial to find ACH_total

Then we can find CADR of the device. The results of each air cleaner trial are then averaged together, and error is expressed as the standard error of the mean.

Fitting the curve

I fit the curve with the least squares method. This method is more accurate when fitting a line, rather than an exponential curve. So with a bit of math, the exponential decay equation is converted to a linear equation, and that equation is used to do the fitting.

Concentration_t = Concentration_bgd + Concentration_t0 * e^(-ACH * t / 3600)
# Assume Concentration_bgd is zero
Concentration_t = 0 + Concentration_t0 * e^(-ACH * t / 3600)
# Take the natural log of both sides
ln(Concentration_t) = ln(Concentration_t0 * e^(ACH * t / 3600))
ln(Concentration_t) = ln(Concentration_t0) * ACH * t / 3600

So, take the natural log of the pm2.5 values and fit it to the right-hand side of that equation using the least squares method.

A python notebook for performing this analysis on files from Sensirion ControlCenter is provided at https://github.com/robwiss/analyze_cadr/blob/main/sensirion_sps30/analyze_cadr_vd_first.ipynb

 

 

 

 



 
 

Friday, April 20, 2018

Futures and Promises in bash (sort of)

Futures and promises can be approximated in bash with the help of a named pipe, process substitution, and i/o redirection. Here's a way to do it:

#!/bin/bash

# get a name for the fifo in /tmp
fifo_name=$(mktemp -u)
# create the fifo
mkfifo -m 600 ${fifo_name}
# redirect this process's fd 3 to cat the fifo
# this is sort of like making fd 3 into a future
exec 3< <(cat ${fifo_name})

borf() {
  sleep 1
  # write to the fifo (like writing to the promise)
  echo "borf" > ${fifo_name}
}

# run the borf function in the background
borf &

echo "waiting for borf..."
# read from fd 3, this will block (like waiting on the value of the future)
read <&3 line
echo $line

This technique can be used to wait asynchronously for a value when there is a need to background an operation and wait for its result.

Thursday, October 13, 2011

Change Ubuntu 11.04 Screen Resolution in Virtualbox

To change screen resolution in Ubuntu 11.04 in Virtualbox:

1. Install Virtualbox Guest additions. Devices→Install Guest Additions...
2. Configure X

Ubuntu 11.04 no longer has the /etc/X11/xorg.conf file that you may be familiar with using to configure your video driver and screen resolution. In Ubuntu 11.04 the sections of the xorg.conf file are split apart into separate conf files in the /usr/share/X11/xorg.conf.d/ directory.

My default 11.04 installation's /usr/share/X11/xorg.conf.d/ contains:
10-evdev.conf
11-evdev-quirks.conf
50-synaptics.conf
50-vmmouse.conf
50-wacom.conf
51-synaptics-quirks.conf

These conf files correspond to devices that were autodetected by X and may be different on your system.

To configure the screen resolution we need to add a section for the monitor. Using your favorite editor create a file called /usr/share/X11/xorg.conf.d/10-monitor.conf.
sudo gedit /usr/share/X11/xorg.conf.d/10-monitor.conf

Enter the following into the conf file:
Section "Monitor"
Identifier "Monitor0"
EndSection

Section "Device"
Identifier "Device0"
Driver "vboxvideo"
EndSection

Section "Screen"
Identifier "Screen0"
Device "Device0"
Monitor "Monitor0"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1400x768"
EndSubSection
EndSection

Where I put 1400x768 choose whatever resolution you desire and reboot the VM.

If something goes wrong and X does not start after you reboot you'll be stuck at the initialization screen. To revert your change press ctrl+alt+f1 to enter a console. Login with your regular credentials and tweak the /usr/share/X11/xorg.conf.d/10-monitor.conf file. You may have chosen a resolution or bit depth that the virtual box driver isn't happy with. My install wouldn't run with a 32 bit depth.

To see more information to help you solve any problems, run
less /var/log/Xorg.0.log

This log will show you the problems that X had while trying to load. I used the log to read the autodetected settings and figure out that I needed to use the "vboxvideo" driver.

Hope this helps.

Friday, August 13, 2010

.NET Remoting and Multiple Interfaces/NICs/networks

Have you ever tried to run a .NET remoting application on a machine with more than one interface? Don't!

But if you have to, you'll soon run into a problem I find completely inexplicable. .NET remoting communications don't necessarily respond to communications on the interface/subnet that initiated those communications.

Imagine you have a machine with two interfaces. One is on subnet 192.168.1.0/24 and the other is on 192.168.2.0/24. Now this machine receives a remoting message on 192.168.1.0/24. .NET may send a reply out the 192.168.1.0/24 interface indicating that all future comms should be handled on the 192.168.2.0/24 interface. All future comms will time out since there is no path between the machines that way. My question is what gives? Why on earth would .NET change what interface to use? In what case does that make any sense at all?

Fortunately, there is a solution. You can bind your remoting channels to a specific interface if you provide the IP of that interface when you create the channel. Here's an example of a channel created bound to an interface.

using System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider;
using System.Runtime.Remoting.Channels.Tcp.TcpChannel;

System.Collections.IDictionary dict = new System.Collections.Hashtable();
dict["name"] = "MyChannel";
dict["bindTo"] = "192.168.1.1"; // This is the important bit!
dict["port"] = 67891;
dict["exclusiveAddressUse"] = false; // allows port to be reused if the channel is deleted and recreated

var serverSink = new BinaryServerFormatterSinkProvider();
serverSink.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
TcpChannel serverChannel = new TcpChannel(dict, null, serverSink);

ChannelServices.RegisterChannel(serverChannel);

Now, when an application requests a service from the channel at port 67891, .NET will always talk over the 192.168.1.1 interface.

This really sucks because now you have to specify what interface to use before you can set up your remoting channels. The answers I can think of are to stick it in a config file (which is probably the intended use case since most .NET examples show pretty extensive use of config files), specify it on the command line, or set up a socket that a client must initially connect to so that the app can use the socket API to figure out what interface it needs to talk to the client on. If you know a better way please tell me.

In looking at references while writing this, I've found that the machineName property might be useful for this purpose as well. See the second reference for more info.

Reference:
http://msdn.microsoft.com/en-us/library/bb397831.aspx
http://msdn.microsoft.com/en-us/library/bb397840.aspx

Friday, June 25, 2010

xcopy is always what you wanted

Today I need to copy over a hidden file. To make things confusing for linux users the xcopy switch for this is /h. Help is /?.

Thursday, June 24, 2010

Expanding a Windows VM System partition

There are two jobs to do to expand a Windows VM's System partition. The first is to expand the virtual disk. The second is to expand the guest OS's partition to include the newly added drive space.

Expanding the Virtual Disk
  1. Shutdown the VM if it is running.

  2. To expand the virtual disk, use vmware-vdiskmanager on the relevant vmdk file.
    I was resizing a 4Gb drive to be 6Gb so for me the command was:

    vmware-vdiskmanager -x 6GB Windows.vmdk

    The vmware-vdiskmanager.exe tool should be located in the Program Files/VMWare/VMWare Server directory. The tool will chug for a while and report that it has resized your drive.

Expand the Guest OS's Partition
To perform this step I made use of the gparted utility that comes with Ubuntu. There is a Gparted linux distro. I tried it but had problems loading it in VMware. I'm sure you can use any livecd that contains the gparted utility.
  1. Get the ubuntu iso (I used 9.10 because I had it lying around) and set it to be in your CD drive in the VM options (accessible while the VM is off by choosing "Edit Virtual Machine Settings").

  2. Start the VM. Click the VM quickly as soon as it begins to load and press ESC to enter the boot menu. Choose Boot from CD and then try out ubuntu without installing it (that's ubuntu's livecd mode).

  3. Once you're in ubuntu press alt-f2 to open the run menu and launch gnome-terminal.

  4. From the terminal launch sudo gparted.

  5. In gparted, click the divider between your existing partition and the unallocated space and drag it over to give the unallocated space to the system partition. Save your changes and close gparted.

  6. Shutdown the VM.

  7. Start the VM and allow it to boot into Windows like it usually does. Windows will do a disk check and boot with your newly resized drive.


References:

Tuesday, February 23, 2010

Hosting/Embedding an Ironpython 2.x app in your C# app

We have a C# library we exercise with some python scripts. We also have the ability to execute our scripts from inside the C# app we use with our library. So instead of writing python scripts and executing them with ipy.exe we created a custom script runner with a bit of a framework attached to it. I set out to upgrade us from IPy 1.1.2 to the latest stable, 2.6.

I had a hell of a time finding all the right resources to figure out how to host/embed Ironpython inside a C# app so I'm writing this post with the aim of showing how to do it.

First things first, when you're looking for help, you need to look for help on Microsoft's Dynamic Language Runtime (DLR) just as much as you need to look for help on Ironpython. In fact, if you only look for Ironpython you'll find tons of examples for the Ironpython 1.x api which don't work at all in the 2.x framework.

To embed in your C# app you should know about three parts of the DLR, ScriptingHost, ScriptEngine, and ScriptScope.

ScriptingHost is tied to an AppDomain (must admit here that I don't know what an AppDomain is, I'm not a C# guy). ScriptEngine is analogous to an instance of an interpreter. ScriptScope is analogous to a context for the execution of bits of code. An instance of a ScriptScope provides a way to separate execution context for multiple scripts on one ScriptEngine instance. A ScriptScope instance also contains global variables and probably lots of other stuff I don't know about.

Here's an outline of what we do when we embed and execute scripts.
1. Load assemblies so that they are accessible from script code
2. Add CPython libraries to the path
3. Execute common script functionality
4. Allow execution of script files
5. Exit scripts cleanly
6. Provide detailed stack trace with file and line number when errors occur

All of this functionality is wrapped up inside a class.

Here's the code.

public class Host
{
private static Host ScriptingHost;

private ScriptEngine engine;
private ScriptScope scope;

public static void InitializeHosting()
{
ScriptingHost = HostFactory();
}

public static Host GetScriptingHost()
{
return ScriptingHost;
}

private Host()
{
ScriptRuntime runtime = IronPython.Hosting.Python.CreateRuntime();

runtime.LoadAssembly( System.Reflection.Assembly.GetExecutingAssembly() ); // reference to current assembly
runtime.LoadAssembly(typeof(System.Diagnostics.Debug).Assembly); //reference to clr

engine = runtime.GetEngine("Python");

if (Directory.Exists("C:\\Python26\\Lib"))
AddToPath("C:\\Python26\\Lib\\");

scope = engine.CreateScope();

engine.Execute("import clr", scope);
engine.Execute("clr.AddReference('MyLib')", scope);
engine.Execute("clr.AddReference('System')", scope);
engine.Execute("import System", scope);
engine.Execute("import MyLib", scope);
}

public void AddToPath(string path)
{
ICollection paths = engine.GetSearchPaths();
paths.Add(path);
engine.SetSearchPaths(paths);
}

public int ExecuteFile(string s, string [] args, bool rethrow)
{
int retCode = 0;

scope.SetVariable("args", args); // we access this variable in our scripts
try
{
engine.ExecuteFile(s, scope);
}
catch (Microsoft.Scripting.SyntaxErrorException e)
{
Console.WriteLine(string.Format("{0}:{1}:{2}", e.SourcePath, e.Line, e.Message));

retCode = 1;

if (rethrow)
{
Exception e2 = new Exception(e.Message, e);
throw e2;
}
}
catch (IronPython.Runtime.Exceptions.SystemExitException e)
{
Object nonIntegerExitCode;
int exitCode = e.GetExitCode(out nonIntegerExitCode);
if (nonIntegerExitCode != null)
{
Console.WriteLine(nonIntegerExitCode.ToString());
}
else
{
retCode = exitCode;
}
}
catch (Exception e)
{
string pyTrace = "";

// get the stackframes associated with the exception
Microsoft.Scripting.Runtime.DynamicStackFrame[] stackframes;
stackframes = Microsoft.Scripting.Runtime.ScriptingRuntimeHelpers.GetDynamicStackFrames(e);

// store off each line
foreach (Microsoft.Scripting.Runtime.DynamicStackFrame frame in stackframes)
{
pyTrace += " " + frame.ToString() + "\n";
}

Console.WriteLine(string.Format("Exception caught while running {0}:\n{1}\nStack Trace:\n{2}", s, e.Message, pyTrace));

retCode = 1;

if (rethrow)
{
Exception e2 = new Exception(e.Message, e);
throw e2;
}
}

return retCode;
}
}


Pay special attention to the bit about how to grab the python stacktrace from the exception and that the CPython libs were added to the search path.

If you're like me you'll fire this framework up and run an old script only to get stopped dead on the error 'type' object has no attribute 'CreateArray'. CreateArray was never part of System.Array, but through the use of some reflection I did not take the time to understand, it was there via IronPython.Runtime.Operations.ArrayOps. Thankfully, we don't even need the CreateArray function anymore. Code that used to say:

newData = [random.randint(0,255) for k in range(1024)]
newData = System.Array.CreateArray(System.Byte,newData)

Can now be written as:

newData = System.Array[System.Byte]([random.randint(0,255) for k in range(1024)])


Now you should be able to upgrade your IronPython 1.x code or simply embed IronPython 2.x in your app. There might be other stuff I run into but this was the meat of it.

Here's some links that were helpful to me:
http://devhawk.net/CategoryView,category,IronPython.aspx - This guy discusses using the SetTrace feature of the engine to implement your own debugger or enable PDB.
http://dlr.codeplex.com/wikipage?title=Docs%20and%20specs - The DLR spec docs (can be found by searching for dlr-spec-hosting.doc, some older links point you to dlr-spec-hosting.pdf which no longer exists)
http://blogs.msdn.com/seshadripv/default.aspx - MSDN blog that has lots of good examples and help. Doesn't seem to have been updated for quite some time though.
http://www.mail-archive.com/users@lists.ironpython.com/ - Archive of the ironpython mailing list, where your question has probably already been debated and answered.