#!/usr/bin/python3

import collections
import json
import os
import subprocess
import re

import psutil

def _build_iep_gpio_entry(pin, active_high=True):
    return {
        "kind": "iep-gpio",
        "pin": pin,
        "active_high": active_high,
    }

def _build_rj45_entry(name, speed=1000, duplex=True):
    return {
        "name": name,
        "speed": speed,
        "duplex": duplex,
    }

def _build_interfacing_led_entry(colour, configurable=True):
    return {
        "colour": colour,
        "configurable": configurable,
    }

_MODELS = {
    "_common": {
        "networking": {
        },
        "interfacing": {
        },
        "tracking": {
        },
    },
    "_gps-garmin": {
        "tracking": {
            "gps": [
                {
                    "interface": {
                        "kind": "rs232",
                        "protocol": "nmea",
                        "device": "/dev/3d-p/gps/0",
                    },
                    "control": {
                        "power": _build_iep_gpio_entry(5),
                    },
                },
            ],
        },
    },
    "_radio-80211": {
    },
    "_radio-BC": {
    },
    "_radio-LTE": {
    },
    
    #VM
    ###
    "VM": {
        "inherits": (
            "_common",
        ),
    },
    
    #r4 bases
    #########
    "_r4": {
        "inherits": (
            "_common",
        ),
        "interfacing": {
            "led": [
                _build_interfacing_led_entry('green'),
                _build_interfacing_led_entry('red'),
            ],
        },
    },
    "_e200": {
        "inherits": (
            "_r4",
        ),
        "networking": {
            "rj45": [_build_rj45_entry(name) for name in ("eif0", "eif1", "eif2", "eif3",)],
        },
        "interfacing": {
            "gpio": {
                "input-output": [_build_iep_gpio_entry(pin) for pin in (0, 1, 2, 3, 4, 8, 9, 10, 11)],
            },
            "rs232": [
                {
                    "device": "/dev/3d-p/serial/0",
                },
                {
                    "device": "/dev/3d-p/serial/1",
                },
                {
                    "device": "/dev/3d-p/serial/2",
                },
                {
                    "device": "/dev/3d-p/serial/3",
                },
                {
                    "device": "/dev/3d-p/serial/4",
                },
                {
                    "device": "/dev/3d-p/serial/5",
                },
            ],
            "can": [
                {
                    "name": "can0",
                },
                {
                    "name": "can1",
                },
            ],
        },
    },
    "_e201": {
        "inherits": (
            "_e200",
        ),
    },
    
    "_cisco-switch-2020_r4": {
        "networking": {
            "cisco-switch-2020": [
                {
                    "name": "cisco0",
                    "rj45_bridge": {
                        "speed": 1000,
                        "duplex": True,
                    },
                    "control": {
                        "configuration": {
                            "kind": "rs232",
                            "protocol": "IOS",
                            "device": "/dev/3d-p/c2020/console",
                        },
                        "reset": _build_iep_gpio_entry(4),
                    },
                },
            ],
        },
    },
    
    "_gps-garmin_r4": {
        "inherits": (
            "_gps-garmin",
        ),
    },
    
    "_lsm6ds3_r4": {
        "tracking": {
            "accelerometer": [
                {
                    "interface": {
                        "kind": "lsm6ds3",
                        "device_id": "0",
                    },
                },
            ],
            "gyroscope": [
                {
                    "interface": {
                        "kind": "lsm6ds3",
                        "device_id": "0",
                    },
                },
            ],
        },
    },
    
    "_radio-BC_r4": {
        "inherits": (
            "_radio-BC",
        ),
        "interfacing": {
            "led": [
                _build_interfacing_led_entry('rajant', configurable=False),
                _build_interfacing_led_entry('red'),
            ],
        },
        "networking": {
            "rajant-breadcrumb": [
                {
                    "name": "rajant0",
                    "kind": "ME4",
                    "rj45_bridge": {
                        "speed": 1000,
                        "duplex": True,
                    },
                    "control": {
                        "configuration": {
                            "kind": "bcconfig", #is there non-software-defined variant info here that could be useful?
                        },
                        "power": _build_iep_gpio_entry(12),
                        "zeroise": _build_iep_gpio_entry(13, active_high=False),
                    },
                },
            ],
            "rj45": [_build_rj45_entry(name) for name in ("eif0", "eif1", "eif2",)],
        },
    },
    
    "_radio-80211_r4_W04_single": {
        "inherits": (
            "_radio-80211",
        ),
        "networking": {
            "802.11": [
                {
                    "name": "wif0",
                    "kind": "W04",
                    "control": {
                        "rfkill": {
                            "kind": "rfkill",
                        },
                    },
                },
            ],
        },
    },
    "_radio-80211_r4_W04_double": {
        "inherits": (
            "_radio-80211",
        ),
        "networking": {
            "802.11": [
                {
                    "name": "wif0",
                    "kind": "W04",
                    "control": {
                        "rfkill": {
                            "kind": "rfkill",
                        },
                    },
                },
                {
                    "name": "wif1",
                    "kind": "W04",
                    "control": {
                        "rfkill": {
                            "kind": "rfkill",
                        },
                    },
                },
            ],
        },
    },
    
    "_radio-LTE_r4": {
        "inherits": (
            "_radio-LTE",
        ),
    },
    "_radio-LTE_r4_L01": {
        "inherits": (
            "_radio-LTE_r4",
        ),
        "networking": {
            "cellular": [
                {
                    "name": "cif0",
                    "kind": "L01",
                },
            ],
        },
    },
    "_radio-LTE_r4_L02": {
        "inherits": (
            "_radio-LTE_r4",
        ),
        "networking": {
            "cellular": [
                {
                    "name": "cif0",
                    "kind": "L02",
                },
            ],
        },
    },
    
    #r4 models
    ##########
    "HOR-R4E2__GBR01G2": {
        "inherits": (
            "_e200",
            "_gps-garmin_r4",
        ),
    },
    "HOR-R4E2__GBR02G2": {
        "inherits": (
            "_e200",
            "_gps-garmin_r4",
            "_radio-80211_r4_W04_single",
        ),
    },
    "HOR-R4E2__GBR05G2": {
        "inherits": (
            "_e200",
            "_gps-garmin_r4",
            "_radio-80211_r4_W04_double",
        ),
    },
    "HOR-R4E2__GBR06G2": {
        "inherits": (
            "_e200",
            "_gps-garmin_r4",
            "_radio-LTE_r4_L02",
            "_radio-80211_r4_W04_single",
        ),
    },
    "HOR-R4E2__GBR07G2": {
        "inherits": (
            "_e200",
            "_gps-garmin_r4",
            "_radio-LTE_r4_L01",
            "_radio-80211_r4_W04_single",
        ),
    },
    "HOR-R4E2__GBR10G2": {
        "inherits": (
            "_e200",
            "_gps-garmin_r4",
            "_radio-BC_r4",
        ),
    },
    "HOR-R4CS__GBR10G2A": {
        "inherits": (
            "_e200",
            "_cisco-switch-2020_r4",
            "_gps-garmin_r4",
            "_radio-BC_r4",
            "_lsm6ds3_r4",
        ),
        "networking": {
            "rajant-breadcrumb": [
                {
                    "name": "rajant0",
                    "kind": "ME4",
                    "rj45_bridge": None,
                    "control": {
                        "configuration": {
                            "kind": "bcconfig", #is there non-software-defined variant info here that could be useful?
                        },
                        "power": _build_iep_gpio_entry(12),
                        "zeroise": _build_iep_gpio_entry(13, active_high=False),
                    },
                },
            ],
            "rj45": [_build_rj45_entry(name) for name in ("eif0",)],
        },
        "interfacing": {
            "gpio": {
                "input-output": [_build_iep_gpio_entry(pin) for pin in (2, 3, 8, 9, 10, 11)],
                "input": [_build_iep_gpio_entry(pin) for pin in (6, 7)],
            },
        },
    },
    "HOR-R4CS__GBR11G2A": {
        "inherits": (
            "HOR-R4CS__GBR10G2A",
        ),
    },

    "HOR-R4P2__GBR01G2": {
        "inherits": (
            "_e201",
            "_gps-garmin_r4",
        ),
    },
    "HOR-R4P2__GBR05G2": {
        "inherits": (
            "_e201",
            "_gps-garmin_r4",
            "_radio-80211_r4_W04_double",
        ),
    },
    "HOR-R4P2__GBR06G2": {
        "inherits": (
            "_e201",
            "_gps-garmin_r4",
            "_radio-LTE_r4_L02",
            "_radio-80211_r4_W04_single",
        ),
    },
    "HOR-R4P2__GBR07G2": {
        "inherits": (
            "_e201",
            "_gps-garmin_r4",
            "_radio-LTE_r4_L01",
            "_radio-80211_r4_W04_single",
        ),
    },
    "HOR-R4P2__GBR10G2": {
        "inherits": (
            "_e201",
            "_gps-garmin_r4",
            "_radio-BC_r4",
        ),
    },
    "HOR-R4P2CS__GBR11G2A": {
        "inherits": (
            "_e201",
            "_cisco-switch-2020_r4",
            "_gps-garmin_r4",
            "_radio-BC_r4",
            "_lsm6ds3_r4",
        ),
        "networking": {
            "rajant-breadcrumb": [
                {
                    "name": "rajant0",
                    "kind": "ME4",
                    "rj45_bridge": None,
                    "control": {
                        "configuration": {
                            "kind": "bcconfig", #is there non-software-defined variant info here that could be useful?
                        },
                        "power": _build_iep_gpio_entry(12),
                        "zeroise": _build_iep_gpio_entry(13, active_high=False),
                    },
                },
            ],
            "rj45": [_build_rj45_entry(name) for name in ("eif0",)],
        },
        "interfacing": {
            "gpio": {
                "input-output": [_build_iep_gpio_entry(pin) for pin in (2, 3, 8, 9, 10, 11)],
                "input": [_build_iep_gpio_entry(pin) for pin in (6, 7)],
            },
        },
    },
    
    
    #r5 bases
    #########
    "_r5": {
        "inherits": (
            "_common",
        ),
        "interfacing": {
            "gpio": {
                "input": [_build_iep_gpio_entry(pin) for pin in (2, 3)],
            },
            "led": [
                _build_interfacing_led_entry('green'),
                _build_interfacing_led_entry('red'),
            ],
        },
    },
    "_e110": {
        "inherits": (
            "_r5",
        ),
        "networking": {
            "rj45": [_build_rj45_entry(name, speed=100) for name in ("eif0", "eif1",)],
        },
    },
    "_e211": {
        "inherits": (
            "_r5",
        ),
        "networking": {
            "rj45": [_build_rj45_entry(name, speed=1000) for name in (
                "eif0",
                "eif1",
                "eif2",
                "eif3",
                "eif4",
                "eif5",
            )],
        },
        "interfacing": {
            "rs232": [
                {
                    "device": "/dev/3d-p/serial/0",
                },
                {
                    "device": "/dev/3d-p/serial/1",
                },
                {
                    "device": "/dev/3d-p/serial/2",
                },
                {
                    "device": "/dev/3d-p/serial/3",
                },
                {
                    "device": "/dev/3d-p/serial/4",
                },
                {
                    "device": "/dev/3d-p/serial/5",
                },
            ],
            "can": [
                {
                    "name": "can0",
                },
                {
                    "name": "can1",
                },
            ],
        },
    },
    
    "_cisco-switch-2020_r5": {
        "networking": {
            "rj45": [_build_rj45_entry(name, speed=1000) for name in (
                "eif0",
            )], #no eif1-5 when a Cisco switch is in use
            "cisco-switch-2020": [
                {
                    "name": "cisco0",
                    "rj45_bridge": {
                        "speed": 1000,
                        "duplex": True,
                    },
                    "control": {
                        "configuration": {
                            "kind": "rs232",
                            "protocol": "IOS",
                            "device": "/dev/3d-p/c2020/console",
                        },
                        "reset": _build_iep_gpio_entry(0),
                    },
                },
            ],
        },
    },

    "_gps-garmin_r5": {
        "tracking": {
            "gps": [
                {
                    "interface": {
                        "kind": "rs232",
                        "protocol": "nmea",
                        "device": "/dev/3d-p/gps/0",
                    },
                    "control": {
                        "power": _build_iep_gpio_entry(1),
                    },
                },
            ],
        },
    },

    "_lsm6ds3_r5": {
        "tracking": {
            "accelerometer": [
                {
                    "interface": {
                        "kind": "lsm6ds3",
                        "device_id": "6",
                    },
                },
            ],
            "gyroscope": [
                {
                    "interface": {
                        "kind": "lsm6ds3",
                        "device_id": "6",
                    },
                },
            ],
        },
    },
    
    "_radio-80211_r5_W04_single": {
        "inherits": (
            "_radio-80211",
        ),
        "networking": {
            "802.11": [
                {
                    "name": "wif0",
                    "kind": "W04",
                    "control": {
                        "rfkill": {
                            "kind": "rfkill",
                        },
                    },
                },
            ],
        },
    },
    
    "_radio-BC_r5": {
        "inherits": (
            "_radio-BC",
        ),
        "interfacing": {
            "led": [
                _build_interfacing_led_entry('rajant', configurable=False),
                _build_interfacing_led_entry('red'),
            ],
        },
    },
    "_radio-BC_r5_E25": {
        "inherits": (
            "_radio-BC_r5",
        ),
        "networking": {
            "rajant-breadcrumb": [
                {
                    "name": "rajant0",
                    "kind": "E25",
                    "rj45_bridge": None,
                    "control": {
                        "configuration": {
                            "kind": "bcconfig", #is there non-software-defined variant info here that could be useful?
                        },
                        "zeroise": _build_iep_gpio_entry(4, active_high=False),
                    },
                },
            ],
        },
    },
        "_radio-BC_r5_ME4": {
        "inherits": (
            "_radio-BC_r5",
        ),
        "networking": {
            "rajant-breadcrumb": [
                {
                    "name": "rajant0",
                    "kind": "ME4",
                    "rj45_bridge": None,
                    "control": {
                        "configuration": {
                            "kind": "bcconfig", #is there non-software-defined variant info here that could be useful?
                        },
                        "power": _build_iep_gpio_entry(5),
                        "zeroise": _build_iep_gpio_entry(4, active_high=False),
                    },
                },
            ],
        },
    },
    
    "_radio-LTE_r5": {
        "inherits": (
            "_radio-LTE",
        ),
    },
    "_radio-LTE_r5_L01": {
        "inherits": (
            "_radio-LTE_r5",
        ),
        "networking": {
            "cellular": [
                {
                    "name": "cif0",
                    "kind": "L01",
                    "control": {
                        "antenna-select": _build_iep_gpio_entry(8),
                        "sar-backoff": _build_iep_gpio_entry(10, active_high=False),
                        "power": _build_iep_gpio_entry(11, active_high=False),
                        "rfkill": _build_iep_gpio_entry(9, active_high=False),
                    },
                },
            ],
        },
    },
    "_radio-LTE_r5_L02": {
        "inherits": (
            "_radio-LTE_r5",
        ),
        "networking": {
            "cellular": [
                {
                    "name": "cif0",
                    "kind": "L02",
                    "control": {
                        "antenna-select": _build_iep_gpio_entry(8),
                        "sar-backoff": _build_iep_gpio_entry(10, active_high=False),
                        "power": _build_iep_gpio_entry(11, active_high=False),
                        "rfkill": _build_iep_gpio_entry(9, active_high=False),
                    },
                },
            ],
        },
    },
    "_radio-LTE_r5_L03": {
        "inherits": (
            "_radio-LTE_r5",
        ),
        "networking": {
            "cellular": [
                {
                    "name": "cif0",
                    "kind": "L03",
                    "control": {
                        "antenna-select": _build_iep_gpio_entry(8),
                        "sar-backoff": _build_iep_gpio_entry(10, active_high=False),
                        "power": _build_iep_gpio_entry(11, active_high=False),
                        "rfkill": _build_iep_gpio_entry(9, active_high=False),
                    },
                },
            ],
        },
    },
    "_radio-LTE_r5_L04": {
        "inherits": (
            "_radio-LTE_r5",
        ),
        "networking": {
            "cellular": [
                {
                    "name": "cif0",
                    "kind": "L04",
                    "control": {
                        "antenna-select": _build_iep_gpio_entry(8),
                        "sar-backoff": _build_iep_gpio_entry(10, active_high=False),
                        "power": _build_iep_gpio_entry(11, active_high=False),
                        "rfkill": _build_iep_gpio_entry(9, active_high=False),
                    },
                },
            ],
        },
    },
    
    
    #e110 models
    ############
    "OSP-R5LE-R1-C1": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L01",
        ),
    },
    "OSP-R5LE-R1-C2": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L02",
        ),
    },
    "OSP-R5LE-R1-C3": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L03",
        ),
    },
    "OSP-R5LE-R1-C4": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L04",
        ),
    },
    
    "OSP-R5L-R1-C1": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L01",
        ),
    },
    "OSP-R5L-R1-C2": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L02",
        ),
    },
    "OSP-R5L-R1-C3": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L03",
        ),
    },
    "OSP-R5L-R1-C4": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L04",
        ),
    },
    
    "OSP-R5LW-R1-C1": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L01",
            "_radio-80211_r5_W04_single",
        ),
    },
    "OSP-R5LW-R1-C2": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L02",
            "_radio-80211_r5_W04_single",
        ),
    },
    "OSP-R5LW-R1-C3": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L03",
            "_radio-80211_r5_W04_single",
        ),
    },
    "OSP-R5LW-R1-C4": {
        "inherits": (
            "_e110",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L04",
            "_radio-80211_r5_W04_single",
        ),
    },
    
    
    #e211 models
    ############
    "ODL-R5CM-R1-C1": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_cisco-switch-2020_r5",
            "_radio-BC_r5_ME4",
            "_lsm6ds3_r5",
        ),
    },

    "ODL-R5LE-R1-C1": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L01",
        ),
    },
    "ODL-R5LE-R1-C2": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L02",
        ),
    },
    "ODL-R5LE-R1-C3": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L03",
        ),
    },
    "ODL-R5LE-R1-C4": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-BC_r5_E25",
            "_radio-LTE_r5_L04",
        ),
    },


    "ODL-R5LE-R2-C1": {
        "inherits": (
            "ODL-R5LE-R1-C1",
            "_lsm6ds3_r5",
        ),
    },
    "ODL-R5LE-R2-C2": {
        "inherits": (
            "ODL-R5LE-R1-C2",
            "_lsm6ds3_r5",
        ),
    },
    "ODL-R5LE-R2-C3": {
        "inherits": (
            "ODL-R5LE-R1-C3",
            "_lsm6ds3_r5",
        ),
    },
    "ODL-R5LE-R2-C4": {
        "inherits": (
            "ODL-R5LE-R1-C4",
            "_lsm6ds3_r5",
        ),
    },


    "ODL-R5LW-R1-C1": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L01",
            "_radio-80211_r5_W04_single",
        ),
    },
    "ODL-R5LW-R1-C2": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L02",
            "_radio-80211_r5_W04_single",
        ),
    },
    "ODL-R5LW-R1-C3": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L03",
            "_radio-80211_r5_W04_single",
        ),
    },
    "ODL-R5LW-R1-C4": {
        "inherits": (
            "_e211",
            "_gps-garmin_r5",
            "_radio-LTE_r5_L04",
            "_radio-80211_r5_W04_single",
        ),
    },


    "ODL-R5LW-R2-C1": {
        "inherits": (
            "ODL-R5LW-R1-C1",
            "_lsm6ds3_r5",
        ),
    },
    "ODL-R5LW-R2-C2": {
        "inherits": (
            "ODL-R5LW-R1-C2",
            "_lsm6ds3_r5",
        ),
    },
    "ODL-R5LW-R2-C3": {
        "inherits": (
            "ODL-R5LW-R1-C3",
            "_lsm6ds3_r5",
        ),
    },
    "ODL-R5LW-R2-C4": {
        "inherits": (
            "ODL-R5LW-R1-C4",
            "_lsm6ds3_r5",
        ),
    },
}


def _merge(base, overlay):
    result = base.copy()
    
    #head-recursion
    for ancestor in overlay.pop('inherits', ()):
        result = _merge(result, _MODELS[ancestor].copy())
        
    for (key, value) in overlay.items():
        if key not in result: #Nothing to merge: just copy the tree
            result[key] = value
        elif isinstance(value, collections.abc.Mapping): #Walk the tree
            if key not in result:
                result[key] = {}
            result[key] = _merge(result[key], value)
        else: #If it's a leaf, clobber the underlying values
            if isinstance(value, collections.abc.Sequence):
                if len(value) == 0: #It's empty; drop it
                    del result[key]
                    continue
            result[key] = value
    return result
    
def _assemble_model(model):
    base_data = {}
    return _merge(base_data, _MODELS[model].copy())
    
    
_ROLES = (
    ('HOR-', 'hornet'),
    ('MTK-', 'datalogger'),
    ('OSP-', 'osprey'),
    ('ODL-', 'osprey-datalogger'),
    ('PHM-', 'phantom'),
    ('SAB-', 'saber'),
    ('VM', 'virtual-machine'),
)

def _run(*args):
    return subprocess.run(args, capture_output=True).stdout.strip().decode('utf-8')
    
def _get_hw_values_disks():
    device_primary = None
    device_secondary = None
    for partition in psutil.disk_partitions():
        if partition.mountpoint == '/':
            device_primary = partition.device
        elif partition.mountpoint == '/var':
            device_secondary = partition.device
            
    def _resolve_block_device_from_partition(partition):
        if partition is None:
            return None
            
        partition = os.path.basename(partition)
        for candidate in reversed(sorted(os.listdir('/sys/block'))): #longest entry first to ensure a correct match
            if partition.startswith(candidate):
                return os.path.join('/sys/block', candidate)
        return None #should not be possible, but fine if it happens
    device_primary = _resolve_block_device_from_partition(device_primary)
    device_secondary = _resolve_block_device_from_partition(device_secondary)
    if device_primary == device_secondary:
        device_secondary = None #single-device system
        
    def _resolve_device_details(device):
        disk_size_raw = int(open(os.path.join(device, 'size'), 'r').read().strip()) * 512 #sectors-to-bytes
        
        disk_gb = 0
        disk_size = disk_size_raw
        while disk_size >= 2000000000: #round up to the nearest 4GB mark
            disk_size -= 4000000000
            disk_gb += 4
            
        disk_details = {
            "device": device,
            "bytes": disk_size_raw,
            "GB_approximate": disk_gb,
        }
        for key in ('model', 'vendor'):
            candidate_path = os.path.join(device, 'device', 'key')
            if os.path.isfile(candidate_path):
                disk_details[key] = open(candidate_path).read().strip()
        return disk_details
        
    disks = {}
    if device_primary:
        disks['primary'] = _resolve_device_details(device_primary)
    if device_secondary:
        disks['secondary'] = _resolve_device_details(device_secondary)
    return {
        "disks": disks,
    }
    
def _get_hw_values(data, model):
    data.update(_get_hw_values_disks())
    
    #TODO: introspect data; if there's anything that can be further fleshed out,
    #like specific 802.11 features, augment it
    
    if model == 'VM':
        #auto-detect Ethernet interfaces
        iface_re = re.compile('^eif\d+$')
        rj45_ifaces = []
        for iface_candidate in os.listdir('/sys/class/net/'):
            if iface_re.match(iface_candidate):
                rj45_ifaces.append(iface_candidate)
        if rj45_ifaces:
            data_rj45 = data["model"]["networking"]["rj45"] = []
            for rj45_iface in sorted(rj45_ifaces):
                data_rj45.append(_build_rj45_entry(rj45_iface))
                
                
def _get_os_values(data, model):
    for (prefix, role) in _ROLES:
        if model.startswith(prefix):
            break
    else:
        raise ValueError("Unrecognised model: {}".format(model))
        
    data.update({
        "iep": {
            "vendor": "3D-P",
            "role": role,
            "architecture": open("/etc/3d-p/firmware/architecture").read().strip(),
            "family": open("/etc/3d-p/firmware/family").read().strip(),
            "model": model,
            "platform": open("/etc/3d-p/firmware/platform").read().strip(),
            "serial": open("/etc/3d-p/firmware/serial").read().strip(),
            "version": open("/etc/3d-p/firmware/version").read().strip(),
        },
        "kernel": {
            "release": _run('uname', '-r'),
            "architecture": _run('uname', '-m'),
        },
    })
    
if __name__ == "__main__":
    model = open("/etc/3d-p/firmware/model").read().strip()
    model = re.sub(r'(HOR-R4(?:E2|P2|CS|P2CS)?)\d{2}GB', r'\g<1>__GB', model)
    
    data = {
        "model": _assemble_model(model),
    }
    _get_hw_values(data, model)
    _get_os_values(data, model)
    
    print(json.dumps(data, sort_keys=True, indent=4))
