diff --git a/edosl0util/cli/rdr2l0.py b/edosl0util/cli/rdr2l0.py
index d40814c7e507185f0ced477be1a1953f8d7bcbb5..061f69532566115b01d41959c1197466b44cacb2 100644
--- a/edosl0util/cli/rdr2l0.py
+++ b/edosl0util/cli/rdr2l0.py
@@ -8,6 +8,7 @@ __copyright__ = "Copyright (C) 2015 University of Wisconsin SSEC. All rights res
 import os
 import glob
 import logging
+import tempfile
 from datetime import datetime, timedelta
 
 from edosl0util import stream, merge, jpssrdr
@@ -16,81 +17,157 @@ from edosl0util.cli import util
 LOG = logging
 
 
+satellite_to_scid = {
+    'snpp': 157,
+}
+
+
+def pdsfilename(product, created):
+    if len(product) < 20:
+        product = product + 'A'*(20-len(product))
+    return '{}XT{:%y%j%H%M%S}001.PDS'.format(product, created)
+
+
+def remove_files(files):
+    [os.remove(f) for f in files]
+
+
+def _do_dump(filepat, product, rdrs, start, end):
+    for filepath in rdrs:
+        LOG.info('dumping %s', filepath)
+        jpssrdr.write_rdr_datasets(filepath)
+
+    # alphanumeric sorting to bootstrap final sort
+    inputs = sorted(glob.glob(filepat))
+    streams = [stream.jpss_packet_stream(open(f, 'rb')) for f in inputs]
+
+    pdsname = pdsfilename(product, start)
+    LOG.info('merging to %s', pdsname)
+    with open(pdsname, 'wb') as dest:
+        merge.merge(streams, output=dest, trunc_to=[start, end])
+    return pdsname
+
+
+def cris_hsk(satellite, rdrs, start, end):
+    product = 'P{}1280CRISHSK'.format(satellite_to_scid[satellite])
+    return _do_dump('*.telemetry.pkts', product, rdrs, start, end)
+
+
+def cris_dwell(satellite, rdrs, start, end):
+    product = 'P{}1291CRISDWELL'.format(satellite_to_scid[satellite])
+    return _do_dump('*.dwell.pkts', product, rdrs, start, end)
+
+
+def cris_sci(satellite, rdrs, start, end):
+    product = 'P{}1289CRISSCIENCE'.format(satellite_to_scid[satellite])
+    return _do_dump('*.science.pkts', product, rdrs, start, end)
+
+
+def atms_hsk(satellite, rdrs, start, end):
+    product = 'P{}0518ATMSHSK'.format(satellite_to_scid[satellite])
+    return _do_dump('*.telemetry.pkts', product, rdrs, start, end)
+
+
+def atms_dwell(satellite, rdrs, start, end):
+    product = 'P{}0517ATMSDWELL'.format(satellite_to_scid[satellite])
+    return _do_dump('*.dwell.pkts', product, rdrs, start, end)
+
+
+def atms_sci(satellite, rdrs, start, end):
+    product = 'P{}0515ATMSSCIENCE'.format(satellite_to_scid[satellite])
+    return _do_dump('*.science.pkts', product, rdrs, start, end)
+
+
+def viirs_sci(satellite, sci, start, end):
+    product = 'P{}0826VIIRSSCIENCE'.format(satellite_to_scid[satellite])
+    return _do_dump('*.science.pkts', product, rdrs, start, end)
+
+
 def main():
     # XXX: This currently uses standard CCSDS packet merging that does not have
     #      any context regarding fill packets. In the future if it is desired to
     #      keep fill packets a new RDR specific merge will be required.
     parser = util.default_parser()
     parser.description = __doc__
+    parser.add_argument('--leave-pkts', action='store_true',
+                        help='Do not delete intermediate .pkts files')
     parser.add_argument(
-        '--minutes', type=int, default=120,
-        help=('Output size in minutes. The resulting file will be truncated '
-              'to created + minutes.'))
-    parser.add_argument(
-        '--science', action='store_true',
-        help='Dump science dataset')
-    parser.add_argument(
-        '--ancillary', action='store_true',
-        help='Dump spacecraft ancillary datasets')
+        '-S', '--satellite', choices=['snpp'], default='snpp',
+        help='Satellite used to set SCID')
     def timestamp(v):
         return datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
     parser.add_argument(
-        '--created', type=timestamp, default=datetime.utcnow(),
-        help=('Time to use for creation time (yyyy-mm-dd hh:mm:ss).'))
-    parser.add_argument('rdr', nargs='+')
+        '-s', '--start', type=timestamp, required=True,
+        help=('File start time. Data before this time will be dropped. This '
+              'time also determines the creation time in the filename. Format '
+              'is YYYY-mm-dd HH:MM:SS.'),
+    )
+    parser.add_argument(
+        '-e', '--end', nargs='?', type=timestamp,
+        help=('File end time. Data after this time will be dropped. If omitted '
+              'end will default to start + 2 hours. Format '
+              'is YYYY-mm-dd HH:MM:SS.'),
+    )
+    subs = parser.add_subparsers(title='Destination level 0 data type')
+
+    def cmd_cris_hsk(args):
+        cris_hsk(args.satellite, args.rcrit, args.start, args.end)
+    subp = subs.add_parser('CRISHSK')
+    subp.add_argument('rcrit', nargs='+')
+    subp.set_defaults(func=cmd_cris_hsk)
+
+    def cmd_cris_dwell(args):
+        return cris_dwell(args.satellite, args.rdrs, args.start, args.end)
+    subp = subs.add_parser('CRISDWELL')
+    subp.add_argument(
+        'rdrs', nargs='+',
+        help=('RCRIH, RCRIM, and RCRII files. The same number of each is '
+              'required to produce a valid L0 file.'))
+    subp.set_defaults(func=cmd_cris_dwell)
+
+    def cmd_cris_sci(args):
+        return cris_sci(args.satellite, args.rcris, args.start, args.end)
+    subp = subs.add_parser('CRISSCIENCE')
+    subp.add_argument('rcris', nargs='+')
+    subp.set_defaults(func=cmd_cris_sci)
+
+    def cmd_atms_hsk(args):
+        return atms_hsk(args.satellite, args.ratmt, args.start, args.end)
+    subp = subs.add_parser('ATMSHSK')
+    subp.add_argument('ratmt', nargs='+')
+    subp.set_defaults(func=cmd_atms_hsk)
+
+    def cmd_atms_dwell(args):
+        return atms_dwell(args.satellite, args.rdrs, args.start, args.end)
+    subp = subs.add_parser('ATMSDWELL')
+    subp.add_argument(
+        'rdrs', nargs='+',
+        help=('RATMW and RATMM files. The same number of each is required '
+              'to produce a valid L0 file.'))
+    subp.set_defaults(func=cmd_atms_dwell)
+
+    def cmd_atms_sci(args):
+        return atms_sci(args.satellite, args.ratms, args.start, args.end)
+    subp = subs.add_parser('ATMSSCIENCE')
+    subp.add_argument('ratms', nargs='+')
+    subp.set_defaults(func=cmd_atms_sci)
+
+    def cmd_viirs_sci(args):
+        return viirs_sci(args.satellite, args.rvirs, args.start, args.end)
+    subp = subs.add_parser('VIIRSSCIENCE')
+    subp.add_argument('rvirs', nargs='+')
+    subp.set_defaults(func=cmd_viirs_sci)
+
     args = parser.parse_args()
+
+    if not args.end:
+        args.end = args.start + timedelta(hours=2)
+
     util.configure_logging(args)
 
-    def pdsfilename(product):
-        return 'P157{}XT{:%y%j%H%M%S}001.PDS'.format(product, args.created)
-
-    def remove_files(files):
-        [os.remove(f) for f in files]
-
-    for filepath in args.rdr:
-        dirname, rdrname = os.path.split(filepath)
-        LOG.info("dumping %s", filepath)
-        jpssrdr.write_rdr_datasets(
-            filepath,
-            science=args.science,
-            ancillary=args.ancillary,
-            skipfill=True)
-
-    interval = [args.created, args.created + timedelta(minutes=args.minutes, microseconds=-1)]
-    LOG.info("merge and truncate to %s", interval)
-
-    if args.ancillary:
-        for apid in (0, 8, 11):
-            inputs = sorted(glob.glob('*.anc{:d}.pkts'.format(apid)))
-            product = '{:04d}AAAAAAAAAAA'.format(apid)
-            pdsname = pdsfilename(product)
-            LOG.info("merging apid %d to %s", apid, pdsname)
-            # there are potentially a very large number of inputs (RNSCA) so
-            # make sure to keep track of files so we can explicitly close them
-            files = [open(f, 'rb') for f in inputs]
-            streams = [stream.jpss_packet_stream(f) for f in files]
-            with open(pdsname, 'wb') as dest:
-                merge.merge(streams, output=dest, trunc_to=interval)
-            for f in files:
-                f.close()
-            remove_files(inputs)
-
-    science_products = {
-        'RNSCA-RVIRS': '0826VIIRSSCIENCE',
-        'RCRIS-RNSCA': '1289CRISSCIENCEA',
-        'RATMS-RNSCA': '0515ATMSSCIENCEA'}
-    if args.science:
-        file_type = os.path.basename(args.rdr[0]).split('_')[0]
-        if file_type not in science_products:
-            parser.exit(1, '%s is not a supported science file type\n' % file_type)
-        inputs = sorted(glob.glob('*.science.pkts'))
-        product = science_products[file_type]
-        pdsname = pdsfilename(product)
-        LOG.info("merging %s to %s", product, pdsname)
-        streams = [stream.jpss_packet_stream(open(f, 'rb')) for f in inputs]
-        with open(pdsname, 'wb') as dest:
-            merge.merge(streams, output=dest, trunc_to=interval)
-        remove_files(inputs)
+    pdsname = args.func(args)
+    if not args.leave_pkts:
+        remove_files(glob.glob('*.pkts'))
 
 
 if __name__ == '__main__':
diff --git a/edosl0util/jpssrdr.py b/edosl0util/jpssrdr.py
index 71a8d3652b6a265d2c4a68706cbd3d421f949807..20da569eb0ff79e82058dca0d670ada447f2bfce 100644
--- a/edosl0util/jpssrdr.py
+++ b/edosl0util/jpssrdr.py
@@ -155,11 +155,37 @@ def _generate_packet_datasets(group):
         yield name, np.array(ds)
 
 
+def _find_data_group(fobj, name, sensors=['viirs', 'cris', 'atms']):
+    for sensor in sensors:
+        group = fobj.get('/All_Data/{}-{}-RDR_All'.format(sensor.upper(), name.upper()))
+        if group:
+            return group
+
+
 def _find_science_group(fobj):
-    for sensor in ['viirs', 'cris', 'atms']:
-        group = fobj.get('/All_Data/{}-SCIENCE-RDR_All'.format(sensor.upper()))
+    return _find_data_group(fobj, 'SCIENCE')
+
+
+def _find_dwell_group(fobj):
+    for name in ('HSKDWELL', 'IMDWELL', 'SSMDWELL'):
+        group = _find_data_group(fobj, name, sensors=['cris'])
         if group:
             return group
+    group = _find_data_group(fobj, 'DWELL', sensors=['atms'])
+    if group:
+        return group
+
+
+def _find_diag_group(fobj):
+    return _find_data_group(fobj, 'DIAGNOSTIC', sensors=['cris', 'atms'])
+
+
+def _find_telemetry_group(fobj):
+    return _find_data_group(fobj, 'TELEMETRY', sensors=['cris', 'atms'])
+
+
+def _find_spacecraft_group(fobj):
+    return _find_data_group(fobj, 'SPACESCRAFT', sensors=['cris', 'atms'])
 
 
 def _rdrs_for_packet_dataset(group):
@@ -177,10 +203,13 @@ def _rdrs_for_packet_dataset(group):
 
 def rdr_datasets(filepath):
     fobj = H5File(filepath)
-    rdr = {}
-    rdr['science'] = _rdrs_for_packet_dataset(_find_science_group(fobj))
-    rdr['ancillary'] = _rdrs_for_packet_dataset(
-        fobj.get('/All_Data/SPACECRAFT-DIARY-RDR_All'))
+    rdr = dict(
+        telemetry=_rdrs_for_packet_dataset(_find_telemetry_group(fobj)),
+        diagnostic=_rdrs_for_packet_dataset(_find_diag_group(fobj)),
+        dwell=_rdrs_for_packet_dataset(_find_dwell_group(fobj)),
+        science=_rdrs_for_packet_dataset(_find_science_group(fobj)),
+        ancillary=_rdrs_for_packet_dataset(_find_spacecraft_group(fobj)),
+    )
     fobj.close()
     return rdr
 
@@ -222,26 +251,19 @@ def _write_packets(pkts, dest, skipfill):
         dest.write(pkt.packet)
 
 
-def write_rdr_datasets(filepath, science=True, ancillary=True, skipfill=False):
+def write_rdr_datasets(filepath, skipfill=False):
     rdrname = os.path.basename(filepath)
     rdrs = rdr_datasets(filepath)
 
-    if science:
-        with open('{}.science.pkts'.format(rdrname), 'wb') as dest:
-            for idx, rdr in enumerate(rdrs['science']):
+    for typekey in rdrs:
+        if not rdrs[typekey]:
+            continue
+        pktfile = '{}.{}.pkts'.format(rdrname, typekey)
+        LOG.debug('writing %s', pktfile)
+        with open(pktfile, 'wb') as dest:
+            for idx, rdr in enumerate(rdrs[typekey]):
                 LOG.debug(
-                    'writing science gran %d %s-%s-%s',
+                    '... %s gran %d %s-%s-%s', typekey,
                     idx, rdr.header.satellite, rdr.header.sensor, rdr.header.type_id)
                 _write_packets(rdr.packets(), dest, skipfill)
-
-    if ancillary:
-        for idx, rdr in enumerate(rdrs['ancillary']):
-            packets = {a.value: rdr.packets_for_apid(a)
-                       for a in rdr.apids}
-            for apid, pkts in packets.items():
-                LOG.debug(
-                    'writing ancillary gran %d %s-%s-%s %d',
-                    idx, rdr.header.satellite, rdr.header.sensor,
-                    rdr.header.type_id, apid)
-                with open('{}.anc{}.pkts'.format(rdrname, apid), 'wb') as dest:
-                    _write_packets(pkts, dest, skipfill)
+    return rdrs
diff --git a/edosl0util/merge.py b/edosl0util/merge.py
index af4b604f1c8691c2c545cb833d9b4cc508fc91b9..c55dd67e7efa931b0ed84c8640e0803ae3ab40ec 100644
--- a/edosl0util/merge.py
+++ b/edosl0util/merge.py
@@ -42,7 +42,7 @@ class _Ptr(object):
             (that.stamp, that.apid)
         )
 
-    # instances with same stamp/apid will compare the same
+    # instances with same stamp/apid/size will compare the same
     def __hash__(self):
         return hash((self.stamp, self.apid, self.size))
 
@@ -94,6 +94,9 @@ def _sort_by_time_apid(index, order=None):
 
 
 def _filter_duplicates_by_size(index):
+    """
+    Take the packet with the largest size.
+    """
     filtered = OrderedDict()
     for ptr in index:
         key = (ptr.stamp, ptr.apid)