/*
 * Modified: Jachym, Meteotemplate, 2017
 */
/*!
 * Created by Mark Crossley, July 2011
 *  see scriptVer below for latest release
 *
 * Released under GNU GENERAL PUBLIC LICENSE, Version 2, June 1991
 * See the enclosed License file
 */
/*!
* Tiny Pub/Sub - v0.7.0 - 2013-01-29
* https://github.com/cowboy/jquery-tiny-pubsub
* Copyright (c) 2013 "Cowboy" Ben Alman; Licensed MIT
*/
(function ($) {
    'use strict';
    var o = $({});
    $.subscribe = function () {o.on.apply(o, arguments);};
    $.unsubscribe = function () {o.off.apply(o, arguments);};
    $.publish = function () {o.trigger.apply(o, arguments);};
}(jQuery));
var gauges = (
function () {
    'use strict';
    var strings = LANG.IT,         //Set to your default language. Store all the strings in one object
        config = {
            scriptVer          : '2.5.17',
            weatherProgram     : 4, // set to MB, but using custom Meteotemplate file instead
            imgPathURL         : './images/',
            oldGauges          : 'gauges.htm',
            realtimeInterval   : 30,
            longPoll           : false,
            gaugeMobileScaling : 0.85,
            graphUpdateTime    : 15,
            stationTimeout     : 11,
            pageUpdateLimit    : 99999,//period after which the page stops automatically updating, in minutes (default 20)
            pageUpdatePswd     : 'its-me',
            digitalFont        : false,digitalForecast    : false,            showPopupData  : true,            showPopupGraphs    : false,
            mobileShowGraphs   : false,
            showWindVariation  : true,
            showWindMetar      : false,
            showIndoorTempHum  : false,
            showCloudGauge     : false,
            showUvGauge        : false,            showSolarGauge        : false,            showSunshineLed    : true,
            showRoseGauge      : true,
            showRoseGaugeOdo   : true,
            showRoseOnDirGauge : true,
            showGaugeShadow        : false,            roundCloudbaseVal  : true,
            realTimeUrlLongPoll: 'realtimegauges-longpoll.php',
            realTimeUrlCumulus : '',
            realTimeUrlWD      : '',
            realTimeUrlVWS     : '',
            realTimeUrlWC      : '',
            realTimeUrlMB      : 'ssMeteotemplate.php',
            realTimeUrlWView   : '',
            realTimeUrlWeewx   : '',
            useCookies         : false,
            tipImages          : [],
            dashboardMode      : false,
            dewDisplayType     : 'app'
        },
        gaugeGlobals = {
            minMaxArea            : 'rgba(212,132,134,0.6)',
            windAvgArea           : 'rgba(212,132,134,0.6)',
            windVariationSector   : 'rgba(120,200,120,0.7)',
            frameDesign           : steelseries.FrameDesign.SHINY_METAL,
            background            : steelseries.BackgroundColor.WHITE,
            foreground            : steelseries.ForegroundType.TYPE2,
            pointer               : steelseries.PointerType.TYPE11,
            pointerColour         : steelseries.ColorDef.GRAY,
            dirAvgPointer         : steelseries.PointerType.TYPE11,
            dirAvgPointerColour   : steelseries.ColorDef.BLUE,
            gaugeType             : steelseries.GaugeType.TYPE4,
            lcdColour             : steelseries.LcdColor.BLUE2,
            knob                  : steelseries.KnobType.STANDARD_KNOB,
            knobStyle             : steelseries.KnobStyle.BLACK,
            labelFormat           : steelseries.LabelNumberFormat.STANDARD,
            tickLabelOrientation  : steelseries.TickLabelOrientation.HORIZONTAL,
            rainUseSectionColours : false,
            rainUseGradientColours: false,
            tempTrendVisible      : true,            pressureTrendVisible  : true,            uvLcdDecimals         : 1,            sunshineThreshold     : 50,
            sunshineThresholdPct  : 75,
            tempScaleDefMinC      : -30.0,
            tempScaleDefMaxC      : 40.0,
            tempScaleDefMinF      : 0.0,
            tempScaleDefMaxF      : 120.0,
            baroScaleDefMinhPa    : 980,
            baroScaleDefMaxhPa    : 1040,
            baroScaleDefMinkPa    : 99,
            baroScaleDefMaxkPa    : 103,
            baroScaleDefMininHg   : 28.00,
            baroScaleDefMaxinHg   : 32.00,
            windScaleDefMaxMph    : 60.0,
            windScaleDefMaxKts    : 60.0,
            windScaleDefMaxMs     : 30.0,
            windScaleDefMaxKmh    : 80.0,
            rainScaleDefMaxmm     : 25.0,
            rainScaleDefMaxIn     : 2.00,
            rainRateScaleDefMaxmm : 100.0,
            rainRateScaleDefMaxIn : 8.00,
            uvScaleDefMax         : 12.0,
            solarGaugeScaleMax    : 1440.0,
            cloudScaleDefMaxft    : 3000,
            cloudScaleDefMaxm     : 1000,
            shadowColour          : 'rgba(220,220,220,0.3)'
        },
        commonParams = {
            fullScaleDeflectionTime: 4,
            gaugeType              : gaugeGlobals.gaugeType,
            minValue               : 0,
            niceScale              : true,
            ledVisible             : false,
            frameDesign            : gaugeGlobals.frameDesign,
            backgroundColor        : gaugeGlobals.background,
            foregroundType         : gaugeGlobals.foreground,
            pointerType            : gaugeGlobals.pointer,
            pointerColor           : gaugeGlobals.pointerColour,
            knobType               : gaugeGlobals.knob,
            knobStyle              : gaugeGlobals.knobStyle,
            lcdColor               : gaugeGlobals.lcdColour,
            lcdDecimals            : 1,
            digitalFont            : config.digitalFont,
            tickLabelOrientation   : gaugeGlobals.tickLabelOrientation,
            labelNumberFormat      : gaugeGlobals.labelFormat
        },
        firstRun = true,
        userUnitsSet = false,
        data = {},
        tickTockInterval,
        ajaxDelay = config.longPoll ? Math.max(config.realtimeInterval - 20, 0) : config.realtimeInterval,
        downloadTimer,
        timestamp = 0,
        jqXHR = null,
        displayUnits = null,
        sampleDate,
        realtimeVer,
        programLink = ['','','', '','','',''],
        ledIndicator, statusScroller, statusTimer,
        gaugeTemp, gaugeDew, gaugeRain, gaugeRRate,
        gaugeHum, gaugeBaro, gaugeWind, gaugeDir,
        gaugeUV, gaugeSolar, gaugeCloud, gaugeRose,
        /* _imgBackground,    // Uncomment if using a background image on the gauges */
        init = function (dashboard) {
            switch (config.weatherProgram) {
            case 4:
                realtimeVer = 10;
                config.realTimeURL = config.longPoll ? config.realTimeUrlLongPoll : config.realTimeUrlMB;
                config.showPopupGraphs = false;
                config.showRoseGauge = true;
                config.showCloudGauge = false;
                config.tipImgs = null;
                config.showWindVariation = true;
                break;
            default:
                realtimeVer = 0;
                config.realtimeURL = null;
                config.showPopupGraphs = false;
                config.tipImgs = null;
            }
            if ($(window).width() < 480) {
                config.gaugeScaling = config.gaugeMobileScaling;
                config.showPopupGraphs = config.mobileShowGraphs;
            } else {
                config.gaugeScaling = 1;
            }
            /*
            _imgBackground = document.createElement('img');
            $(_imgBackground).attr('src', config.imgPathURL + 'logoSmall.png');
            */
            displayUnits = getCookie('units');
            if (displayUnits !== null) {
                userUnitsSet = true;
                setRadioCheck('rad_unitsTemp', displayUnits.temp);
                data.tempunit = '°' + displayUnits.temp;
                setRadioCheck('rad_unitsRain', displayUnits.rain);
                data.rainunit = displayUnits.rain;
                setRadioCheck('rad_unitsPress', displayUnits.press);
                data.pressunit = displayUnits.press;
                setRadioCheck('rad_unitsWind', displayUnits.wind);
                data.windunit = displayUnits.wind;
                displayUnits.windrun = getWindrunUnits(data.windunit);
                setRadioCheck('rad_unitsCloud', displayUnits.cloud);
                data.cloudunit = displayUnits.cloud;
            } else {
                displayUnits = {
                    temp   : 'C',
                    rain   : 'mm',
                    press  : 'hPa',
                    wind   : 'km/h',
                    windrun: 'km',
                    cloud  : 'm'
                };
                data.tempunit = '°C';
                data.rainunit = 'mm';
                data.pressunit = 'hPa';
                data.windunit = 'km/h';
                data.cloudunit = 'm';
            }
            if (config.showPopupData) {
                ddimgtooltip.showTips = config.showPopupData;
            }
            if (config.showPopupGraphs) {
                ddimgtooltip.tiparray[0][0] = (config.tipImgs[0][0] === null ? null : '');
                ddimgtooltip.tiparray[1][0] = (config.tipImgs[1][0] === null ? null : '');
                ddimgtooltip.tiparray[2][0] = (config.tipImgs[2]    === null ? null : '');
                ddimgtooltip.tiparray[3][0] = (config.tipImgs[3]    === null ? null : '');
                ddimgtooltip.tiparray[4][0] = (config.tipImgs[4][0] === null ? null : '');
                ddimgtooltip.tiparray[5][0] = (config.tipImgs[5]    === null ? null : '');
                ddimgtooltip.tiparray[6][0] = (config.tipImgs[6]    === null ? null : '');
                ddimgtooltip.tiparray[7][0] = (config.tipImgs[7]    === null ? null : '');
                ddimgtooltip.tiparray[8][0] = (config.tipImgs[8]    === null ? null : '');
                ddimgtooltip.tiparray[9][0] = (config.tipImgs[9]    === null ? null : '');
                ddimgtooltip.tiparray[10][0] = (config.tipImgs[10]  === null ? null : '');
                ddimgtooltip.tiparray[11][0] = (config.tipImgs[11]  === null ? null : '');
            }
            ledIndicator = singleLed.getInstance();
            statusScroller = singleStatus.getInstance();
            statusTimer = singleTimer.getInstance();
            gaugeTemp = singleTemp.getInstance();
            if (gaugeTemp) {gauges.doTemp = gaugeTemp.update;}
            gaugeDew = singleDew.getInstance();
            if (gaugeDew) {gauges.doDew = gaugeDew.update;}
            gaugeHum = singleHum.getInstance();
            if (gaugeHum) {gauges.doHum = gaugeHum.update;}
            gaugeBaro = singleBaro.getInstance();
            gaugeWind = singleWind.getInstance();
            gaugeDir = singleDir.getInstance();
            gaugeRain = singleRain.getInstance();
            gaugeRRate = singleRRate.getInstance();
            if (!config.showUvGauge) {
                $('#canvas_uv').parent().remove();
            } else {
                gaugeUV = singleUV.getInstance();
            }
            if (!config.showSolarGauge) {
                $('#canvas_solar').parent().remove();
            } else {
                gaugeSolar = singleSolar.getInstance();
            }
            if (!config.showRoseGauge) {
                $('#canvas_rose').parent().remove();
            } else {
                gaugeRose = singleRose.getInstance();
                if (gaugeRose) {
                    gaugeRose.setTitle(strings.windrose);
                    gaugeRose.setCompassString(strings.compass);
                }
            }
            if (!config.showCloudGauge) {
                $('#canvas_cloud').parent().remove();
                $('#cloud').parent().remove();
            } else {
                gaugeCloud = singleCloudBase.getInstance();
            }
            changeLang(strings, false);
            if (!dashboard) {
                getRealtime();
                tickTockInterval = setInterval(
                    function () {
                        $.publish('gauges.clockTick', null);
                    },
                    1000);
                if (config.pageUpdateLimit > 0 && getUrlParam('pageUpdate') !== config.pageUpdatePswd) {
                    setTimeout(pageTimeout, config.pageUpdateLimit * 60 * 1000);
                }
            }
        },
        singleLed = (function () {
            var instance;   // Stores a reference to the Singleton
            var led;        // Stores a reference to the SS LED
            function init() {
                // create led indicator
                if ($('#canvas_led').length) {
                    led = new steelseries.Led(
                        'canvas_led', {
                            ledColor: steelseries.LedColor.GREEN_LED,
                            size    : $('#canvas_led').width()
                        });
                    setTitle(strings.led_title);
                }
                function setTitle(newTitle) {
                    $('#canvas_led').attr('title', newTitle);
                }
                function setLedColor(newColour) {
                    if (led) {
                        led.setLedColor(newColour);
                    }
                }
                function setLedOnOff(onState) {
                    if (led) {
                        led.setLedOnOff(onState);
                    }
                }
                function blink(blinkState) {
                    if (led) {
                        led.blink(blinkState);
                    }
                }
                return {
                    setTitle   : setTitle,
                    setLedColor: setLedColor,
                    setLedOnOff: setLedOnOff,
                    blink      : blink
                };
            }
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleStatus = (function () {
            var instance;   // Stores a reference to the Singleton
            var scroller;   // Stores a reference to the SS scrolling display
            function init() {
                // create forecast display
                if ($('#canvas_status').length) {
                    scroller = new steelseries.DisplaySingle(
                        'canvas_status', {
                            width            : $('#canvas_status').width(),
                            height           : $('#canvas_status').height(),
                            lcdColor         : gaugeGlobals.lcdColour,
                            unitStringVisible: false,
                            value            : strings.statusStr,
                            digitalFont      : config.digitalForecast,
                            valuesNumeric    : false,
                            autoScroll       : true,
                            alwaysScroll     : false
                        });
                }
                function setValue(newTxt) {
                    if (scroller) {
                        scroller.setValue(newTxt);
                    }
                }
                return {setText: setValue};
            }
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleTimer = (function () {
            var instance,   // Stores a reference to the Singleton
                lcd,        // Stores a reference to the SS LED
                count = 1;
            function init() {
                function tick() {
                    if (lcd) {
                        lcd.setValue(count);
                        count += config.longPoll ? 1 : -1;
                    }
                }
                function reset(val) {
                    count = val;
                }
                function setValue(newVal) {
                    if (lcd) {
                        lcd.setValue(newVal);
                    }
                }
                // create timer display
                if ($('#canvas_timer').length) {
                    lcd = new steelseries.DisplaySingle(
                        'canvas_timer', {
                            width            : $('#canvas_timer').width(),
                            height           : $('#canvas_timer').height(),
                            lcdColor         : gaugeGlobals.lcdColour,
                            lcdDecimals      : 0,
                            unitString       : strings.timer,
                            unitStringVisible: true,
                            digitalFont      : config.digitalFont,
                            value            : count
                        });
                    // subcribe to data updates
                    $.subscribe('gauges.clockTick', tick);
                }
                return {
                    reset   : reset,
                    setValue: setValue
                };
            }
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleTemp = (function () {
            var instance;
            var ssGauge;
            var cache = {};
            function init() {
                var params = $.extend(true, {}, commonParams);
                cache.sections = createTempSections(true);
                cache.areas = [];
                cache.minValue = gaugeGlobals.tempScaleDefMinC;
                cache.maxValue = gaugeGlobals.tempScaleDefMaxC;
                cache.title = strings.temp_title_out;
                cache.value = gaugeGlobals.tempScaleDefMinC + 0.0001;
                cache.maxMinVisible = false;
                cache.selected = 'out';
                // create temperature radial gauge
                if ($('#canvas_temp').length) {
                    params.size = Math.ceil($('#canvas_temp').width() * config.gaugeScaling);
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.section = cache.sections;
                    params.area = cache.areas;
                    params.minValue = cache.minValue;
                    params.maxValue = cache.maxValue;
                    params.thresholdVisible = false;
                    params.minMeasuredValueVisible = cache.maxMinVisible;
                    params.maxMeasuredValueVisible = cache.maxMinVisible;
                    params.titleString = cache.title;
                    params.unitString = data.tempunit;
                    params.trendVisible = gaugeGlobals.tempTrendVisible;
                    //params.customLayer = _imgBackground;      // uncomment to add a background image - See Logo Images above
                    ssGauge = new steelseries.Radial('canvas_temp', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_temp').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_temp').css(gaugeShadow(params.size));
                    }
                    // remove indoor temperature/humidity options?
                    if (!config.showIndoorTempHum) {
                        $('#rad_temp1').remove();
                        $('#lab_temp1').remove();
                        $('#rad_temp2').remove();
                        $('#lab_temp2').remove();
                        $('#rad_hum1').remove();
                        $('#lab_hum1').remove();
                        $('#rad_hum2').remove();
                        $('#lab_hum2').remove();
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    return null;
                }
                function update() {
                    var sel = cache.selected;
                    if (arguments.length === 1) {
                        sel = arguments[0].value;
                    }
                    var t1, scaleStep, tip;
                    cache.minValue = data.tempunit[1] === 'C' ? gaugeGlobals.tempScaleDefMinC : gaugeGlobals.tempScaleDefMinF;
                    cache.maxValue = data.tempunit[1] === 'C' ? gaugeGlobals.tempScaleDefMaxC : gaugeGlobals.tempScaleDefMaxF;
                    if (sel === 'out') {
                        cache.low = extractDecimal(data.tempTL);
                        cache.high = extractDecimal(data.tempTH);
                        cache.lowScale = getMinTemp(cache.minValue);
                        cache.highScale = getMaxTemp(cache.maxValue);
                        cache.value = extractDecimal(data.temp);
                        cache.title = strings.temp_title_out;
                        cache.loc = strings.temp_out_info;
                        cache.trendVal = extractDecimal(data.temptrend);
                        cache.popupImg = 0;
                        if (gaugeGlobals.tempTrendVisible) {
                            t1 = tempTrend(+cache.trendVal, data.tempunit, false);
                            if (t1 === -9999) {
                                // trend value isn't currently available
                                cache.trend = steelseries.TrendState.OFF;
                            } else if (t1 > 0) {
                                cache.trend = steelseries.TrendState.UP;
                            } else if (t1 < 0) {
                                cache.trend = steelseries.TrendState.DOWN;
                            } else {
                                cache.trend = steelseries.TrendState.STEADY;
                            }
                        }
                    } else {
                        cache.low = extractDecimal(data.intemp);
                        cache.lowScale = cache.low;
                        cache.high = cache.low;
                        cache.highScale = cache.low;
                        cache.value = cache.low;
                        cache.title = strings.temp_title_in;
                        cache.loc = strings.temp_in_info;
                        cache.maxMinVisible = false;
                        cache.popupImg = 1;
                        if (gaugeGlobals.tempTrendVisible) {
                            cache.trend = steelseries.TrendState.OFF;
                        }
                    }
                    // has the gauge type changed?
                    if (cache.selected !== sel) {
                        cache.selected = sel;
                        //Change gauge title
                        ssGauge.setTitleString(cache.title);
                        ssGauge.setMaxMeasuredValueVisible(cache.maxMinVisible);
                        ssGauge.setMinMeasuredValueVisible(cache.maxMinVisible);
                        if (config.showPopupGraphs && config.tipImgs[0][cache.popupImg] !== null) {
                            var cacheDefeat = '?' + $('#imgtip0_img').attr('src').split('?')[1];
                            $('#imgtip0_img').attr('src', config.imgPathURL + config.tipImgs[0][cache.popupImg] + cacheDefeat);
                        }
                    }
                    //auto scale the ranges
                    scaleStep = data.tempunit[1] === 'C' ? 10 : 20;
                    while (cache.lowScale < cache.minValue) {
                        cache.minValue -= scaleStep;
                        if (cache.highScale <= cache.maxValue - scaleStep) {
                            cache.maxValue -= scaleStep;
                        }
                    }
                    while (cache.highScale > cache.maxValue) {
                        cache.maxValue += scaleStep;
                        if (cache.minValue >= cache.minValue + scaleStep) {
                            cache.minValue += scaleStep;
                        }
                    }
                    if (cache.minValue !== ssGauge.getMinValue() || cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setMinValue(cache.minValue);
                        ssGauge.setMaxValue(cache.maxValue);
                        ssGauge.setValue(cache.minValue);
                    }
                    if (cache.selected === 'out') {
                        cache.areas = [steelseries.Section(+cache.low, +cache.high, gaugeGlobals.minMaxArea)];
                    } else {
                        cache.areas = [];
                    }
                    if (gaugeGlobals.tempTrendVisible) {
                        ssGauge.setTrend(cache.trend);
                    }
                    ssGauge.setArea(cache.areas);
                    ssGauge.setValueAnimated(+cache.value);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        if (cache.selected === 'out') {
                            tip = cache.loc + ' - ' + strings.lowestF_info + ': ' + cache.low + data.tempunit + ' ' + strings.at + ' ' + data.TtempTL +
                                 ' | ' +
                                 strings.highestF_info + ': ' + cache.high + data.tempunit + ' ' + strings.at + ' ' + data.TtempTH;
                            if (cache.trendVal !== -9999) {
                                tip += '
' +
                                    strings.temp_trend_info + ': ' + tempTrend(cache.trendVal, data.tempunit, true) +
                                    ' ' + cache.trendVal + data.tempunit + '/h';
                            }
                        } else {
                            tip = cache.loc + ': ' + data.intemp + data.tempunit;
                        }
                        $('#imgtip0_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[0][cache.popupImg] !== null) {
                        $('#imgtip0_img').attr('src', config.imgPathURL + config.tipImgs[0][cache.popupImg] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(), // End singleTemp()
        singleDew = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                var tmp;
                //define dew point gauge start values
                cache.sections = createTempSections(true);
                cache.areas = [];
                cache.minValue = gaugeGlobals.tempScaleDefMinC;
                cache.maxValue = gaugeGlobals.tempScaleDefMaxC;
                cache.value = gaugeGlobals.tempScaleDefMinC + 0.0001;
                // Has the end user selected a preferred 'scale' before
                tmp = getCookie('dewGauge');
                cache.selected = tmp !== null ? tmp : config.dewDisplayType;
                setRadioCheck('rad_dew', cache.selected);
                switch (cache.selected) {
                case 'dew':
                    cache.title = strings.dew_title;
                    cache.popupImg = 0;
                    break;
                case 'app':
                    cache.title = strings.apptemp_title;
                    cache.popupImg = 1;
                    break;
                case 'wnd':
                    cache.title = strings.chill_title;
                    cache.popupImg = 2;
                    break;
                case 'hea':
                    cache.title = strings.heat_title;
                    cache.popupImg = 3;
                    break;
                case 'hum':
                    cache.title = strings.humdx_title;
                    cache.popupImg = 4;
                // no default
                }
                cache.minMeasuredVisible = false;
                cache.maxMeasuredVisible = false;
                // create dew point radial gauge
                if ($('#canvas_dew').length) {
                    params.size = Math.ceil($('#canvas_dew').width() * config.gaugeScaling);
                    params.section = cache.sections;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.area = cache.areas;
                    params.minValue = cache.minValue;
                    params.maxValue = cache.maxValue;
                    params.thresholdVisible = false;
                    params.titleString = cache.title;
                    params.unitString = data.tempunit;
                    ssGauge = new steelseries.Radial('canvas_dew', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_dew').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_dew').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    // if rad isn't specified, just use existing value
                    var sel = cache.selected;
                    // Argument length === 2 when called from event handler
                    if (arguments.length === 1) {
                        sel = arguments[0].value;
                        // save the choice in a cookie
                        setCookie('dewGauge', sel);
                    }
                    var tip, scaleStep;
                    cache.minValue = data.tempunit[1] === 'C' ? gaugeGlobals.tempScaleDefMinC : gaugeGlobals.tempScaleDefMinF;
                    cache.maxValue = data.tempunit[1] === 'C' ? gaugeGlobals.tempScaleDefMaxC : gaugeGlobals.tempScaleDefMaxF;
                    cache.lowScale = getMinTemp(cache.minValue);
                    cache.highScale = getMaxTemp(cache.maxValue);
                    switch (sel) {
                    case 'dew': // dew point
                        cache.low = extractDecimal(data.dewpointTL);
                        cache.high = extractDecimal(data.dewpointTH);
                        cache.value = extractDecimal(data.dew);
                        cache.areas = [steelseries.Section(+cache.low, +cache.high, gaugeGlobals.minMaxArea)];
                        cache.title = strings.dew_title;
                        cache.minMeasuredVisible = false;
                        cache.maxMeasuredVisible = false;
                        cache.popupImg = 0;
                        tip = strings.dew_info + ':' +
                             '
' +
                             '- ' + strings.lowest_info + ': ' + cache.low + data.tempunit + ' ' + strings.at + ' ' + data.TdewpointTL +
                             ' | ' + strings.highest_info + ': ' + cache.high + data.tempunit + ' ' + strings.at + ' ' + data.TdewpointTH;
                        break;
                    case 'app': // apparent temperature
                        cache.low = extractDecimal(data.apptempTL);
                        cache.high = extractDecimal(data.apptempTH);
                        cache.value = extractDecimal(data.apptemp);
                        cache.areas = [steelseries.Section(+cache.low, +cache.high, gaugeGlobals.minMaxArea)];
                        cache.title = strings.apptemp_title;
                        cache.minMeasuredVisible = false;
                        cache.maxMeasuredVisible = false;
                        cache.popupImg = 1;
                        tip = tip = strings.apptemp_info + ':' +
                             '
' +
                             '- ' + strings.lowestF_info + ': ' + cache.low + data.tempunit + ' ' + strings.at + ' ' + data.TapptempTL +
                             ' | ' + strings.highestF_info + ': ' + cache.high + data.tempunit + ' ' + strings.at + ' ' + data.TapptempTH;
                        break;
                    case 'wnd': // wind chill
                        cache.low = extractDecimal(data.wchillTL);
                        cache.high = extractDecimal(data.wchill);
                        cache.value = extractDecimal(data.wchill);
                        cache.areas = [];
                        cache.title = strings.chill_title;
                        cache.minMeasuredVisible = true;
                        cache.maxMeasuredVisible = false;
                        cache.popupImg = 2;
                        tip = strings.chill_info + ':' +
                            '
' +
                            '- ' + strings.lowest_info + ': ' + cache.low + data.tempunit + ' ' + strings.at + ' ' + data.TwchillTL;
                        break;
                    case 'hea': // heat index
                        cache.low = extractDecimal(data.heatindex);
                        cache.high = extractDecimal(data.heatindexTH);
                        cache.value = extractDecimal(data.heatindex);
                        cache.areas = [];
                        cache.title = strings.heat_title;
                        cache.minMeasuredVisible = false;
                        cache.maxMeasuredVisible = true;
                        cache.popupImg = 3;
                        tip = strings.heat_info + ':' +
                            '
' +
                            '- ' + strings.highest_info + ': ' + cache.high + data.tempunit + ' ' + strings.at + ' ' + data.TheatindexTH;
                        break;
                    case 'hum': // humidex
                        cache.low = extractDecimal(data.humidex);
                        cache.high = extractDecimal(data.humidex);
                        cache.value = extractDecimal(data.humidex);
                        cache.areas = [];
                        cache.title = strings.humdx_title;
                        cache.minMeasuredVisible = false;
                        cache.maxMeasuredVisible = false;
                        cache.popupImg = 4;
                        tip = strings.humdx_info + ': ' + cache.value + data.tempunit;
                        break;
                    // no default
                    }
                    if (cache.selected !== sel) {
                        cache.selected = sel;
                        // change gauge title
                        ssGauge.setTitleString(cache.title);
                        // and graph image
                        if (config.showPopupGraphs && config.tipImgs[1][cache.popupImg] !== null) {
                            var cacheDefeat = '?' + $('#imgtip1_img').attr('src').split('?')[1];
                            $('#imgtip1_img').attr('src', config.imgPathURL + config.tipImgs[1][cache.popupImg] + cacheDefeat);
                        }
                    }
                    //auto scale the ranges
                    scaleStep = data.tempunit[1] === 'C' ? 10 : 20;
                    while (cache.lowScale < cache.minValue) {
                        cache.minValue -= scaleStep;
                        if (cache.highScale <= cache.maxValue - scaleStep) {
                            cache.maxValue -= scaleStep;
                        }
                    }
                    while (cache.highScale > cache.maxValue) {
                        cache.maxValue += scaleStep;
                        if (cache.minValue >= cache.minValue + scaleStep) {
                            cache.minValue += scaleStep;
                        }
                    }
                    if (cache.minValue !== ssGauge.getMinValue() || cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setMinValue(cache.minValue);
                        ssGauge.setMaxValue(cache.maxValue);
                        ssGauge.setValue(cache.minValue);
                    }
                    ssGauge.setMinMeasuredValueVisible(cache.minMeasuredVisible);
                    ssGauge.setMaxMeasuredValueVisible(cache.maxMeasuredVisible);
                    ssGauge.setMinMeasuredValue(+cache.low);
                    ssGauge.setMaxMeasuredValue(+cache.high);
                    ssGauge.setArea(cache.areas);
                    ssGauge.setValueAnimated(+cache.value);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        $('#imgtip1_txt').html(tip);
                    }
                }
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[1][cache.popupImg] !== null) {
                        $('#imgtip1_img').attr('src', config.imgPathURL + config.tipImgs[1][cache.popupImg] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(), // End of singleDew()
        singleRain = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define rain gauge start values
                cache.maxValue = gaugeGlobals.rainScaleDefMaxmm;
                cache.value = 0.0001;
                cache.title = strings.rain_title;
                cache.lcdDecimals = 1;
                cache.scaleDecimals = 1;
                cache.labelNumberFormat = gaugeGlobals.labelFormat;
                cache.sections = (gaugeGlobals.rainUseSectionColours ? createRainfallSections(true) : []);
                cache.valGrad = (gaugeGlobals.rainUseGradientColours ? createRainfallGradient(true) : null);
                // create rain radial bargraph gauge
                if ($('#canvas_rain').length) {
                    params.size = Math.ceil($('#canvas_rain').width() * config.gaugeScaling);
                    params.maxValue = cache.maxValue;
                    params.thresholdVisible = false;
                    params.titleString = cache.title;
                    params.unitString = data.rainunit;
                    params.valueColor = steelseries.ColorDef.BLUE;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.valueGradient = cache.valGrad;
                    params.useValueGradient = gaugeGlobals.rainUseGradientColours;
                    params.useSectionColors = gaugeGlobals.rainUseSectionColour;
                    params.useSectionColors = gaugeGlobals.rainUseSectionColours;
                    params.labelNumberFormat = cache.labelNumberFormat;
                    params.fractionalScaleDecimals = cache.scaleDecimals;
                    ssGauge = new steelseries.RadialBargraph('canvas_rain', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_rain').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_rain').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    cache.value = extractDecimal(data.rfall);
                    if (data.rainunit === 'mm') { // 10, 20, 30...
                        cache.maxValue = Math.max(Math.ceil(cache.value / 10) * 10, gaugeGlobals.rainScaleDefMaxmm);
                    } else {
                        // inches 0.5, 1.0, 2.0, 3.0 ... 10.0, 12.0, 14.0
                        if (cache.value < 6) {
                            cache.maxValue = Math.max(Math.ceil(cache.value), gaugeGlobals.rainRateScaleDefMaxIn);
                        } else {
                            cache.maxValue = Math.max(Math.ceil(cache.value / 2) * 2, gaugeGlobals.rainRateScaleDefMaxIn);
                        }
                        cache.scaleDecimals = cache.maxValue < 1 ? 2 : 1;
                    }
                    if (cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setValue(0);
                        ssGauge.setFractionalScaleDecimals(cache.scaleDecimals);
                        ssGauge.setMaxValue(cache.maxValue);
                    }
                    ssGauge.setValueAnimated(cache.value);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        $('#imgtip2_txt').html(strings.LastRain_info + ': ' + data.LastRained);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[2] !== null) {
                        $('#imgtip2_img').attr('src', config.imgPathURL + config.tipImgs[2] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleRRate = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define rain rate gauge start values
                cache.maxMeasured = 0;
                cache.maxValue = gaugeGlobals.rainRateScaleDefMaxmm;
                cache.value = 0.0001;
                cache.title = strings.rrate_title;
                cache.lcdDecimals = 1;
                cache.scaleDecimals = 0;
                cache.labelNumberFormat = gaugeGlobals.labelFormat;
                cache.sections = createRainRateSections(true);
                // create rain rate radial gauge
                if ($('#canvas_rrate').length) {
                    params.size = Math.ceil($('#canvas_rrate').width() * config.gaugeScaling);
                    params.section = cache.sections;
                    params.maxValue = cache.maxValue;
                    params.thresholdVisible = false;
                    params.maxMeasuredValueVisible = true;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.titleString = cache.title;
                    params.unitString = data.rainunit + '/h';
                    params.lcdDecimals = cache.lcdDecimals;
                    params.labelNumberFormat = cache.labelNumberFormat;
                    params.fractionalScaleDecimals = cache.scaleDecimals;
                    params.niceScale = false;
                    ssGauge = new steelseries.Radial('canvas_rrate', params);
                    ssGauge.setMaxMeasuredValue(cache.maxMeasured);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_rrate').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_rrate').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    var tip;
                    cache.value = extractDecimal(data.rrate);
                    cache.maxMeasured = extractDecimal(data.rrateTM);
                    cache.overallMax = Math.max(cache.maxMeasured, cache.value);  // workaround for VWS bug, not supplying correct max value today
                    if (data.rainunit === 'mm') { // 10, 20, 30...
                        cache.maxValue = Math.max(Math.ceil(cache.overallMax / 10) * 10, gaugeGlobals.rainRateScaleDefMaxmm);
                    } else {
                        // inches 0.5, 1.0, 2.0, 3.0 ... 10.0, 12.0, 14.0
                        if (cache.value < 6) {
                            cache.maxValue = Math.max(Math.ceil(cache.value), gaugeGlobals.rainRateScaleDefMaxIn);
                        } else {
                            cache.maxValue = Math.max(Math.ceil(cache.value / 2) * 2, gaugeGlobals.rainRateScaleDefMaxIn);
                        }
                        cache.scaleDecimals = cache.maxValue < 1 ? 2 : 1;
                    }
                    if (cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setValue(0);
                        ssGauge.setFractionalScaleDecimals(cache.scaleDecimals);
                        ssGauge.setMaxValue(cache.maxValue);
                    }
                    ssGauge.setValueAnimated(cache.value);
                    ssGauge.setMaxMeasuredValue(cache.maxMeasured);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        tip = strings.rrate_info + ':
' +
                            '- ' + strings.maximum_info + ': ' + data.rrateTM + ' ' + data.rainunit + '/h ' + strings.at + ' ' + data.TrrateTM;
                        $('#imgtip3_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[3] !== null) {
                        $('#imgtip3_img').attr('src', config.imgPathURL + config.tipImgs[3] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleHum = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define humidity gauge start values
                cache.areas = [];
                cache.value = 0.0001;
                cache.title = strings.hum_title_out;
                cache.selected = 'out';
                // create humidity radial gauge
                if ($('#canvas_hum').length) {
                    params.size = Math.ceil($('#canvas_hum').width() * config.gaugeScaling);
                    params.section = [steelseries.Section(0, 20, 'rgba(139, 117, 0, 0.5)'),
                                      steelseries.Section(20, 40, 'rgba(180, 152, 4, 0.3)'),
                                      steelseries.Section(40, 60, 'rgba(170, 212, 4, 0.38)'),
                                      steelseries.Section(60, 80, 'rgba(0, 180, 18,0.3)'),
                                      steelseries.Section(80, 100, 'rgba(0, 180, 18, 0.5)')];
                    params.area = cache.areas;
                    params.maxValue = 100;
                    params.thresholdVisible = false;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.titleString = cache.title;
                    params.unitString = 'RH%';
                    ssGauge = new steelseries.Radial('canvas_hum', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_hum').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_hum').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    var radio;
                    // Argument length === 2 when called from event handler
                    if (arguments.length === 1) {
                        radio = arguments[0];
                    }
                    //if rad isn't specified, just use existing value
                    var sel = (typeof radio === 'undefined' ? cache.selected : radio.value),
                        tip;
                    if (sel === 'out') {
                        cache.value = extractDecimal(data.hum);
                        cache.areas = [steelseries.Section(+extractDecimal(data.humTL), +extractDecimal(data.humTH), gaugeGlobals.minMaxArea)];
                        cache.title = strings.hum_title_out;
                        cache.popupImg = 0;
                    } else {
                        cache.value = extractDecimal(data.inhum);
                        if (data.inhumTL && data.inhumTH) {
                            cache.areas = [steelseries.Section(+extractDecimal(data.inhumTL), +extractDecimal(data.inhumTH), gaugeGlobals.minMaxArea)];
                        } else {
                            cache.areas = [];
                        }
                        cache.title = strings.hum_title_in;
                        cache.popupImg = 1;
                    }
                    if (cache.selected !== sel) {
                        cache.selected = sel;
                        //Change gauge title
                        ssGauge.setTitleString(cache.title);
                        if (config.showPopupGraphs && config.tipImgs[4][cache.popupImg] !== null) {
                            var cacheDefeat = '?' + $('#imgtip4_img').attr('src').split('?')[1];
                            $('#imgtip4_img').attr('src', config.imgPathURL + config.tipImgs[4][cache.popupImg] + cacheDefeat);
                        }
                    }
                    ssGauge.setArea(cache.areas);
                    ssGauge.setValueAnimated(cache.value);
                    if (ddimgtooltip.showTips) {
                        //update tooltip
                        if (cache.selected === 'out') {
                            tip = strings.hum_out_info + ':' +
                                '
' +
                                '- ' + strings.minimum_info + ': ' + extractDecimal(data.humTL) + '% ' + strings.at + ' ' + data.ThumTL +
                                ' | ' + strings.maximum_info + ': ' + extractDecimal(data.humTH) + '% ' + strings.at + ' ' + data.ThumTH;
                        } else if (data.inhumTL && data.inhumTH) {
                            // we have indoor high/low data
                            tip = strings.hum_in_info + ':' +
                                 '
' +
                                '- ' + strings.minimum_info + ': ' + extractDecimal(data.inhumTL) + '% ' + strings.at + ' ' + data.TinhumTL +
                                ' | ' + strings.maximum_info + ': ' + extractDecimal(data.inhumTH) + '% ' + strings.at + ' ' + data.TinhumTH;
                        } else {
                            // no indoor high/low data
                            tip = strings.hum_in_info + ': ' + extractDecimal(data.inhum) + '%';
                        }
                        $('#imgtip4_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[4][cache.popupImg] !== null) {
                        $('#imgtip4_img').attr('src', config.imgPathURL + config.tipImgs[4][cache.popupImg] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleBaro = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define pressure/barometer gauge start values
                cache.sections = [];
                cache.areas = [];
                cache.minValue = gaugeGlobals.baroScaleDefMinhPa;
                cache.maxValue = gaugeGlobals.baroScaleDefMaxhPa;
                cache.value = cache.minValue + 0.0001;
                cache.title = strings.baro_title;
                cache.lcdDecimals = 1;
                cache.scaleDecimals = 0;
                cache.labelNumberFormat = gaugeGlobals.labelFormat;
                // create pressure/barometric radial gauge
                if ($('#canvas_baro').length) {
                    params.size = Math.ceil($('#canvas_baro').width() * config.gaugeScaling);
                    params.section = cache.sections;
                    params.area = cache.areas;
                    params.minValue = cache.minValue;
                    params.maxValue = cache.maxValue;
                    params.niceScale = false;
                    params.thresholdVisible = false;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.titleString = cache.title;
                    params.unitString = data.pressunit;
                    params.lcdDecimals = cache.lcdDecimals;
                    params.trendVisible = gaugeGlobals.pressureTrendVisible;
                    params.labelNumberFormat = cache.labelNumberFormat;
                    params.fractionalScaleDecimals = cache.scaleDecimals;
                    ssGauge = new steelseries.Radial('canvas_baro', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_baro').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_baro').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    var tip, t1, dps;
                    cache.recLow = +extractDecimal(data.pressL);
                    cache.recHigh = +extractDecimal(data.pressH);
                    cache.todayLow = +extractDecimal(data.pressTL);
                    cache.todayHigh = +extractDecimal(data.pressTH);
                    cache.value = +extractDecimal(data.press);
                    // Convert the WD change over 3 hours to an hourly rate
                    cache.trendVal = +extractDecimal(data.presstrendval) / (config.weatherProgram === 2 ? 3 : 1);
                    if (data.pressunit === 'hPa' || data.pressunit === 'mb') {
                        //  min range 990-1030 - steps of 10 hPa
                        cache.minValue = Math.min(Math.floor((cache.recLow - 2) / 10) * 10, gaugeGlobals.baroScaleDefMinhPa);
                        cache.maxValue = Math.max(Math.ceil((cache.recHigh + 2) / 10) * 10, gaugeGlobals.baroScaleDefMaxhPa);
                        dps = 1; // 1 decimal place
                    } else if (data.pressunit === 'kPa') {
                        //  min range 99-105 - steps of 1 kPa
                        cache.minValue = Math.min(Math.floor(cache.recLow - 0.2), 99);
                        cache.maxValue = Math.max(Math.ceil(cache.recHigh + 0.2), 105);
                        dps = 2;
                    } else {
                        // inHg: min range 29.5-30.5 - steps of 0.5 inHg
                        cache.minValue = Math.min(Math.floor((cache.recLow - 0.1) * 2) / 2, gaugeGlobals.baroScaleDefMininHg);
                        cache.maxValue = Math.max(Math.ceil((cache.recHigh + 0.1) * 2) / 2, gaugeGlobals.baroScaleDefMaxinHg);
                        dps = 3;
                    }
                    cache.trendValRnd = cache.trendVal.toFixed(dps);
                    cache.todayLowRnd = cache.todayLow.toFixed(dps);
                    cache.todayHighRnd = cache.todayHigh.toFixed(dps);
                    if (cache.minValue !== ssGauge.getMinValue() || cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setMinValue(cache.minValue);
                        ssGauge.setMaxValue(cache.maxValue);
                        ssGauge.setValue(cache.minValue);
                    }
                    if (cache.recHigh === cache.todayHigh && cache.recLow === cache.todayLow) {
                        // VWS does not provide record hi/lo values
                        cache.sections = [];
                        cache.areas = [steelseries.Section(cache.todayLow, cache.todayHigh, gaugeGlobals.minMaxArea)];
                    } else {
                        cache.sections = [
                            steelseries.Section(cache.minValue, cache.recLow, 'rgba(255,0,0,0.5)'),
                            steelseries.Section(cache.recHigh, cache.maxValue, 'rgba(255,0,0,0.5)')
                        ];
                        cache.areas = [
                            steelseries.Section(cache.minValue, cache.recLow, 'rgba(255,0,0,0.5)'),
                            steelseries.Section(cache.recHigh, cache.maxValue, 'rgba(255,0,0,0.5)'),
                            steelseries.Section(cache.todayLow, cache.todayHigh, gaugeGlobals.minMaxArea)
                        ];
                    }
                    if (gaugeGlobals.pressureTrendVisible) {
                        // Use the baroTrend rather than simple arithmetic test - steady is more/less than zero!
                        t1 = baroTrend(cache.trendVal, data.pressunit, false);
                        if (t1 === -9999) {
                            // trend value isn't currently available
                            cache.trend = steelseries.TrendState.OFF;
                        } else if (t1 > 0) {
                            cache.trend = steelseries.TrendState.UP;
                        } else if (t1 < 0) {
                            cache.trend = steelseries.TrendState.DOWN;
                        } else {
                            cache.trend = steelseries.TrendState.STEADY;
                        }
                        ssGauge.setTrend(cache.trend);
                    }
                    ssGauge.setArea(cache.areas);
                    ssGauge.setSection(cache.sections);
                    ssGauge.setValueAnimated(cache.value);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        tip = strings.baro_info + ':' +
                            '
' +
                            '- ' + strings.minimum_info + ': ' + cache.todayLowRnd + ' ' + data.pressunit + ' ' + strings.at + ' ' + data.TpressTL +
                            ' | ' + strings.maximum_info + ': ' + cache.todayHighRnd + ' ' + data.pressunit + ' ' + strings.at + ' ' + data.TpressTH;
                        if (cache.trendVal !== -9999) {
                            tip += '
' +
                                '- ' + strings.baro_trend_info + ': ' + baroTrend(cache.trendVal, data.pressunit, true) + ' ' +
                                (cache.trendValRnd > 0 ? '+' : '') + cache.trendValRnd + ' ' + data.pressunit + '/h';
                        }
                        $('#imgtip5_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[5] !== null) {
                        $('#imgtip5_img').attr('src', config.imgPathURL + config.tipImgs[5] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleWind = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define wind gauge start values
                cache.maxValue = gaugeGlobals.windScaleDefMaxKph;
                cache.areas = [];
                cache.maxMeasured = 0;
                cache.value = 0.0001;
                cache.title = strings.wind_title;
                // create wind speed radial gauge
                if ($('#canvas_wind').length) {
                    params.size = Math.ceil($('#canvas_wind').width() * config.gaugeScaling);
                    params.area = cache.areas;
                    params.maxValue = cache.maxValue;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.niceScale = false;
                    params.thresholdVisible = false;
                    params.maxMeasuredValueVisible = true;
                    params.titleString = cache.title;
                    params.unitString = data.windunit;
                    ssGauge = new steelseries.Radial('canvas_wind', params);
                    ssGauge.setMaxMeasuredValue(cache.maxMeasured);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_wind').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_wind').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    var tip;
                    cache.value = extractDecimal(data.wlatest);
                    cache.average = extractDecimal(data.wspeed);
                    cache.gust = extractDecimal(data.wgust);
                    cache.maxGustToday = extractDecimal(data.wgustTM);
                    cache.maxAvgToday = extractDecimal(data.windTM);
                    switch (data.windunit) {
                    case 'mph':
                    case 'kts':
                        cache.maxValue = Math.max(Math.ceil(cache.maxGustToday / 10) * 10, gaugeGlobals.windScaleDefMaxMph);
                        break;
                    case 'm/s':
                        cache.maxValue = Math.max(Math.ceil(cache.maxGustToday / 5) * 5, gaugeGlobals.windScaleDefMaxMs);
                        break;
                    default:
                        cache.maxValue = Math.max(Math.ceil(cache.maxGustToday / 20) * 20, gaugeGlobals.windScaleDefMaxKmh);
                    }
                    cache.areas = [
                        steelseries.Section(0, +cache.average, gaugeGlobals.windAvgArea),
                        steelseries.Section(+cache.average, +cache.gust, gaugeGlobals.minMaxArea)
                    ];
                    if (cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setMaxValue(cache.maxValue);
                    }
                    ssGauge.setArea(cache.areas);
                    ssGauge.setMaxMeasuredValue(cache.maxGustToday);
                    ssGauge.setValueAnimated(cache.value);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        tip = strings.tenminavgwind_info + ': ' + cache.average + ' ' + data.windunit + ' | ' +
                              strings.maxavgwind_info + ': ' + cache.maxAvgToday + ' ' + data.windunit + '
' +
                              strings.tenmingust_info + ': ' + cache.gust + ' ' + data.windunit + ' | ' +
                              strings.maxgust_info + ': ' + cache.maxGustToday + ' ' + data.windunit + ' ' +
                              strings.at + ' ' + data.TwgustTM + ' ' + strings.bearing_info + ': ' + data.bearingTM +
                              (isNaN(parseFloat(data.bearingTM)) ? '' : '° (' + getord(+data.bearingTM) + ')');
                        $('#imgtip6_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[6] !== null) {
                        $('#imgtip6_img').attr('src', config.imgPathURL + config.tipImgs[6] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(), // End of singleWind()
        singleDir = (function () {
            var instance;
            var ssGauge;
            var cache = {};
            function init() {
                var params = $.extend(true, {}, commonParams);
                cache.valueLatest = 0;
                cache.valueAverage = 0;
                cache.titles = [strings.latest_web, strings.tenminavg_web];
                if ($('#canvas_dir').length) {
                    params.size = Math.ceil($('#canvas_dir').width() * config.gaugeScaling);
                    params.pointerTypeLatest = gaugeGlobals.pointer;
                    params.pointerTypeAverage = gaugeGlobals.dirAvgPointer;
                    params.pointerColorAverage = gaugeGlobals.dirAvgPointerColour;
                    params.degreeScale = true;
                    params.pointSymbols = strings.compass;
                    params.roseVisible = false;
                    params.lcdTitleStrings = cache.titles;
                    params.useColorLabels = false;
                    ssGauge = new steelseries.WindDirection('canvas_dir', params);
                    ssGauge.setValueAverage(+cache.valueAverage);
                    ssGauge.setValueLatest(+cache.valueLatest);
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_dir').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    if (config.showGaugeShadow) {
                        $('#canvas_dir').css(gaugeShadow(params.size));
                    }
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    return null;
                }
                function update() {
                    var windSpd, windGst, range, tip, i,
                        rosePoints = 0,
                        roseMax = 0,
                        roseSectionAngle = 0,
                        roseAreas = [];
                    cache.valueLatest = extractInteger(data.bearing);
                    cache.valueAverage = extractInteger(data.avgbearing);
                    cache.bearingFrom = extractInteger(data.BearingRangeFrom10);
                    cache.bearingTo = extractInteger(data.BearingRangeTo10);
                    ssGauge.setValueAnimatedAverage(+cache.valueAverage);
                    if (cache.valueAverage === 0) {
                        cache.valueLatest = 0;
                    }
                    ssGauge.setValueAnimatedLatest(+cache.valueLatest);
                    if (config.showWindVariation) {
                        windSpd = +extractDecimal(data.wspeed);
                        windGst = +extractDecimal(data.wgust);
                        switch (data.windunit.toLowerCase()) {
                        case 'mph':
                            cache.avgKnots = 0.868976242 * windSpd;
                            cache.gstKnots = 0.868976242 * windGst;
                            break;
                        case 'kts':
                            cache.avgKnots = windSpd;
                            cache.gstKnots = windGst;
                            break;
                        case 'm/s':
                            cache.avgKnots = 1.94384449 * windSpd;
                            cache.gstKnots = 1.94384449 * windGst;
                            break;
                        case 'km/h':
                            cache.avgKnots = 0.539956803 * windSpd;
                            cache.gstKnots = 0.539956803 * windGst;
                            break;
                        // no default
                        }
                        cache.avgKnots = Math.round(cache.avgKnots);
                        cache.gstKnots = Math.round(cache.gstKnots);
                        if (config.showWindMetar) {
                            ssGauge.VRB = ' - METAR: ' + ('0' + data.avgbearing).slice(-3) + ('0' + cache.avgKnots).slice(-2) +
                                        'G' + ('0' + cache.gstKnots).slice(-2) + 'KT ';
                        } else {
                            ssGauge.VRB = '';
                        }
                        if (windSpd > 0) {
                            range = (+cache.bearingTo < +cache.bearingFrom ? 360 + (+cache.bearingTo) : +cache.bearingTo) - (+cache.bearingFrom);
                            if (cache.avgKnots < 3) {
                                if (config.showRoseOnDirGauge) {
                                    ssGauge.setSection([steelseries.Section(cache.bearingFrom, cache.bearingTo, gaugeGlobals.windVariationSector)]);
                                    ssGauge.setSection([]);
                                } else {
                                    ssGauge.setSection([steelseries.Section(cache.bearingFrom, cache.bearingTo, gaugeGlobals.minMaxArea)]);
                                    ssGauge.setArea([]);
                                }
                            } else if (config.showRoseOnDirGauge) {
                                ssGauge.setSection([steelseries.Section(cache.bearingFrom, cache.bearingTo, gaugeGlobals.windVariationSector)]);
                            } else {
                                ssGauge.setSection([]);
                                ssGauge.setArea([steelseries.Section(cache.bearingFrom, cache.bearingTo, gaugeGlobals.minMaxArea)]);
                            }
                            if (config.showWindMetar) {
                                if ((range < 60 && range > 0) || range === 0 && cache.bearingFrom === cache.valueAverage) {
                                    ssGauge.VRB += ' STDY';
                                } else if (cache.avgKnots < 3) {
                                    ssGauge.VRB += ' VRB';
                                } else {
                                    ssGauge.VRB += ' ' + cache.bearingFrom + 'V' + cache.bearingTo;
                                }
                            }
                        } else {
                            if (config.showWindMetar) {
                                ssGauge.VRB = ' - METAR: 00000KT';
                            }
                            ssGauge.setSection([]);
                            if (!config.showRoseOnDirGauge) {
                                ssGauge.setArea([]);
                            }
                        }
                    } else {
                        ssGauge.VRB = '';
                    }
                    if (config.showRoseOnDirGauge && data.WindRoseData) {
                        rosePoints = data.WindRoseData.length;
                        roseSectionAngle = 360 / rosePoints;
                        for (i = 0; i < rosePoints; i++) {
                            roseMax = Math.max(roseMax, data.WindRoseData[i]);
                        }
                        if (roseMax > 0) {
                            for (i = 0; i < rosePoints; i++) {
                                roseAreas[i] = steelseries.Section(
                                    i * roseSectionAngle - roseSectionAngle / 2,
                                    (i + 1) * roseSectionAngle - roseSectionAngle / 2,
                                    'rgba(' + gradient('2020D0', 'D04040', data.WindRoseData[i] / roseMax) + ',' +
                                        (data.WindRoseData[i] / roseMax).toFixed(2) + ')'
                                );
                            }
                        }
                        ssGauge.setArea(roseAreas);
                    }
                    if (ddimgtooltip.showTips) {
                        tip = strings.latest_title + ' ' + strings.bearing_info + ': ' + cache.valueLatest + '° (' + getord(+cache.valueLatest) + ')' +
                              ssGauge.VRB + '
' + strings.tenminavg_web + ' ' + strings.bearing_info + ': ' + cache.valueAverage + '° (' +
                              getord(+cache.valueAverage) + '), ' + strings.dominant_bearing + ': ' + data.domwinddir;
                        if (!config.showRoseGauge) {
                            tip += '
' + strings.windruntoday + ': ' + data.windrun + ' ' + displayUnits.windrun;
                        }
                        $('#imgtip7_txt').html(tip);
                    }
                }
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[7] !== null) {
                        $('#imgtip7_img').attr('src', config.imgPathURL + config.tipImgs[7] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            }
            return {
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleRose = (function () {
            var instance;
            var ssGauge;
            var buffers = {};
            var cache = {};
            var ctxRoseCanvas;
            cache.firstRun = true;
            cache.odoDigits = 5;
            function init() {
                var div, roseCanvas;
                if ($('#canvas_rose').length) {
                    cache.gaugeSize = Math.ceil($('#canvas_rose').width() * config.gaugeScaling);
                    cache.gaugeSize2 = cache.gaugeSize / 2;
                    cache.showOdo = config.showRoseGaugeOdo || false;
                                        cache.compassString = strings.compass;
                    div = document.createElement('div');
                    div.style.display = 'none';
                    document.body.appendChild(div);
                    cache.plotSize = Math.floor(cache.gaugeSize * 0.68);
                    cache.plotSize2 = cache.plotSize / 2;
                    buffers.plot = document.createElement('canvas');
                    buffers.plot.width = cache.plotSize;
                    buffers.plot.height = cache.plotSize;
                    buffers.plot.id = 'rosePlot';
                    buffers.ctxPlot = buffers.plot.getContext('2d');
                    div.appendChild(buffers.plot);
                    buffers.frame = document.createElement('canvas');
                    buffers.frame.width = cache.gaugeSize;
                    buffers.frame.height = cache.gaugeSize;
                    buffers.ctxFrame = buffers.frame.getContext('2d');
                    steelseries.drawFrame(buffers.ctxFrame, gaugeGlobals.frameDesign, cache.gaugeSize2, cache.gaugeSize2, cache.gaugeSize, cache.gaugeSize);
                    buffers.background = document.createElement('canvas');
                    buffers.background.width = cache.gaugeSize;
                    buffers.background.height = cache.gaugeSize;
                    buffers.ctxBackground = buffers.background.getContext('2d');
                    steelseries.drawBackground(buffers.ctxBackground, gaugeGlobals.background, cache.gaugeSize2,
                                               cache.gaugeSize2, cache.gaugeSize, cache.gaugeSize);
                    /*
                    if (g_imgSmall !== null) {
                        var drawSize = g_size * 0.831775;
                        var x = (g_size - drawSize) / 2;
                        buffers.ctxBackground.drawImage(g_imgSmall, x, x, cache.gaugeSize, cache.gaugeSize);
                    }
                    */
                    drawCompassPoints(buffers.ctxBackground, cache.gaugeSize);
                    buffers.foreground = document.createElement('canvas');
                    buffers.foreground.width = cache.gaugeSize;
                    buffers.foreground.height = cache.gaugeSize;
                    buffers.ctxForeground = buffers.foreground.getContext('2d');
                    steelseries.drawForeground(buffers.ctxForeground, gaugeGlobals.foreground, cache.gaugeSize, cache.gaugeSize, false);
                    roseCanvas = document.getElementById('canvas_rose');
                    ctxRoseCanvas = roseCanvas.getContext('2d');
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_rose').css({width: cache.gaugeSize + 'px', height: cache.gaugeSize + 'px'});
                    }
                    roseCanvas.width = cache.gaugeSize;
                    roseCanvas.height = cache.gaugeSize;
                    if (config.showGaugeShadow) {
                        $('#canvas_rose').css(gaugeShadow(cache.gaugeSize));
                    }
                    ctxRoseCanvas.drawImage(buffers.frame, 0, 0);
                    ctxRoseCanvas.drawImage(buffers.background, 0, 0);
                    ctxRoseCanvas.drawImage(buffers.foreground, 0, 0);
                    if (cache.showOdo) {
                        cache.odoHeight = Math.ceil(cache.gaugeSize * 0.08);
                        cache.odoWidth = Math.ceil(Math.floor(cache.odoHeight * 0.68) * cache.odoDigits);
                        buffers.Odo = document.createElement('canvas');
                        $(buffers.Odo).attr({
                            id    : 'canvas_odo',
                            width : cache.odoWidth,
                            height: cache.odoHeight
                        });
                        $(buffers.Odo).css({
                            position: 'absolute',
                            top     : Math.ceil(cache.gaugeSize * 0.7 + $('#canvas_rose').position().top) + 'px',
                            left    : Math.ceil((cache.gaugeSize - cache.odoWidth) / 2 + $('#canvas_rose').position().left) + 'px'
                        });
                        $(buffers.Odo).insertBefore('#canvas_rose');
                        ssGauge = new steelseries.Odometer('canvas_odo', {
                            height  : cache.odoHeight,
                            digits  : cache.odoDigits - 1,
                            valueForeColor: "rgb(255, 255, 255)",
                            valueBackColor: "rgb(0, 0, 0)",
                            decimalForeColor: "rgb(255, 0, 0)",
                            decimalBackColor: "rgb(255, 255, 255)",
                            decimals: 1
                        });
                    }
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    return null;
                }
                cache.firstRun = false;
                function update() {
                    var rose, offset;
                    if (ctxRoseCanvas && !cache.firstRun) {
                        ctxRoseCanvas.clearRect(0, 0, cache.gaugeSize, cache.gaugeSize);
                        buffers.ctxPlot.clearRect(0, 0, cache.plotSize, cache.plotSize);
                        rose = new RGraph.Rose('rosePlot', data.WindRoseData);
                        rose.Set('chart.strokestyle', 'black');
                        rose.Set('chart.background.axes.color', 'gray');
                        rose.Set('chart.colors.alpha', 0.5);
                        rose.Set('chart.colors', ['Gradient(#408040:red:#7070A0)']);
                        rose.Set('chart.margin', Math.ceil(40 / data.WindRoseData.length));
                        rose.Set('chart.title', cache.gaugeTitle);
                        rose.Set('chart.title.size', Math.ceil(0.05 * cache.plotSize));
                        rose.Set('chart.title.bold', false);
                        rose.Set('chart.title.color', gaugeGlobals.background.labelColor.getRgbColor());
                        rose.Set('chart.gutter.top', 0.2 * cache.plotSize);
                        rose.Set('chart.gutter.bottom', 0.2 * cache.plotSize);
                        rose.Set('chart.tooltips.effect', 'snap');
                        rose.Set('chart.labels.axes', '');
                        rose.Set('chart.background.circles', true);
                        rose.Set('chart.background.grid.spokes', 16);
                        rose.Set('chart.radius', cache.plotSize2);
                        rose.Draw();
                        ctxRoseCanvas.drawImage(buffers.frame, 0, 0);
                        ctxRoseCanvas.drawImage(buffers.background, 0, 0);
                        offset = Math.floor(cache.gaugeSize2 - cache.plotSize2);
                        ctxRoseCanvas.drawImage(buffers.plot, offset, offset);
                        ctxRoseCanvas.drawImage(buffers.foreground, 0, 0);
                        if (cache.showOdo) {
                            ssGauge.setValueAnimated(extractDecimal(data.windrun));
                        }
                        if (ddimgtooltip.showTips) {
                            $('#imgtip10_txt').html(strings.dominant_bearing + ': ' + data.domwinddir + '
' +
                                                    strings.windruntoday + ': ' + data.windrun + ' ' + displayUnits.windrun);
                        }
                    }
                }
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[10] !== null) {
                        $('#imgtip10_img').attr('src', config.imgPathURL + config.tipImgs[10] + cacheDefeat);
                    }
                }
                function drawCompassPoints(ctx, size) {
                    ctx.save();
                    ctx.font = 0.08 * size + 'px serif';
                    ctx.fillStyle = gaugeGlobals.background.labelColor.getRgbColor();
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    for (var i = 0; i < 4; i++) {
                        ctx.translate(size / 2, size * 0.125);
                        ctx.fillText(cache.compassString[i * 2], 0, 0, size);
                        ctx.translate(-size / 2, -size * 0.125);
                        ctx.translate(size / 2, size / 2);
                        ctx.rotate(Math.PI / 2);
                        ctx.translate(-size / 2, -size / 2);
                    }
                    ctx.restore();
                }
                function setTitle(newTitle) {
                    cache.gaugeTitle = newTitle;
                }
                function setCompassString(newStr) {
                    cache.compassString = newStr;
                    if (!cache.firstRun) {
                        steelseries.drawBackground(buffers.ctxBackground, gaugeGlobals.background, cache.gaugeSize2, cache.gaugeSize2,
                                                   cache.gaugeSize, cache.gaugeSize);
                        drawCompassPoints(buffers.ctxBackground, cache.gaugeSize);
                    }
                }
                return {
                    update           : update,
                    gauge            : ssGauge,
                    drawCompassPoints: drawCompassPoints,
                    setTitle         : setTitle,
                    setCompassString : setCompassString
                };
            }
            return {
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleUV = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define UV start values
                cache.value = 0.0001;
                cache.sections = [
                    steelseries.Section(0, 2.9, '#289500'),
                    steelseries.Section(2.9, 5.8, '#f7e400'),
                    steelseries.Section(5.8, 7.8, '#f85900'),
                    steelseries.Section(7.8, 10.9, '#d8001d'),
                    steelseries.Section(10.9, 20, '#6b49c8')
                ];
                // Define value gradient for UV
                cache.gradient = new steelseries.gradientWrapper(0, 16,
                    [0, 0.1, 0.19, 0.31, 0.45, 0.625, 1],
                    [
                        new steelseries.rgbaColor(0, 200, 0, 1),
                        new steelseries.rgbaColor(0, 200, 0, 1),
                        new steelseries.rgbaColor(255, 255, 0, 1),
                        new steelseries.rgbaColor(248, 89, 0, 1),
                        new steelseries.rgbaColor(255, 0, 0, 1),
                        new steelseries.rgbaColor(255, 0, 144, 1),
                        new steelseries.rgbaColor(153, 140, 255, 1)
                    ]
                );
                cache.useSections = false;
                cache.useValueGradient = true;
                // create UV bargraph gauge
                if ($('#canvas_uv').length) {
                    params.size = Math.ceil($('#canvas_uv').width() * config.gaugeScaling);
                    params.gaugeType = steelseries.GaugeType.TYPE1;
                    params.maxValue = gaugeGlobals.uvScaleDefMax;
                    params.titleString = strings.uv_title;
                    params.niceScale = false;
                    params.section = cache.sections;
                    params.useSectionColors = cache.useSections;
                    params.valueGradient = cache.gradient;
                    params.useValueGradient = cache.useValueGradient;
                    params.lcdDecimals = gaugeGlobals.uvLcdDecimals;
                    ssGauge = new steelseries.RadialBargraph('canvas_uv', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_uv').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_uv').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    var tip, indx;
                    cache.value = extractDecimal(data.UV);
                    if (+cache.value === 0) {
                        indx = 0;
                    } else if (cache.value < 2.5) {
                        indx = 1;
                    } else if (cache.value < 5.5) {
                        indx = 2;
                    } else if (cache.value < 7.5) {
                        indx = 3;
                    } else if (cache.value < 10.5) {
                        indx = 4;
                    } else {
                        indx = 5;
                    }
                    if (cache.value > ssGauge.getMaxValue()) {
                        ssGauge.setMaxValue(Math.ceil(cache.value) + Math.ceil(cache.value) % 2);
                    }
                    cache.risk = strings.uv_levels[indx];
                    cache.headLine = strings.uv_headlines[indx];
                    cache.detail = strings.uv_details[indx];
                    ssGauge.setUnitString(cache.risk);
                    ssGauge.setValueAnimated(cache.value);
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        tip = '' + strings.uv_title + ': ' + cache.value + ' - ' + strings.solar_maxToday + ': ' + data.UVTH + '
';
                        tip += '' + cache.headLine + '
';
                        tip += cache.detail;
                        $('#imgtip8_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[8] !== null) {
                        $('#imgtip8_img').attr('src', config.imgPathURL + config.tipImgs[8] + cacheDefeat);
                    }
                }
                return {
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleSolar = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                // define Solar start values
                cache.value = 0.0001;
                cache.sections = [
                    steelseries.Section(0, 200, 'rgba(47, 47, 47, 0.3)'),
                    steelseries.Section(200, 600, 'rgba(254, 251, 184, 0.3)'),
                    steelseries.Section(600, 800, 'rgba(232, 220, 56, 0.3)'),
                    steelseries.Section(800, 1000, 'rgba(255, 246, 24,0.3)'),
                    steelseries.Section(1000, 1800, 'rgba(255, 246, 24, 0.5)')
                ];
                // create Solar gauge
                if ($('#canvas_solar').length) {
                    params.size = Math.ceil($('#canvas_solar').width() * config.gaugeScaling);
                    params.section = cache.sections;
                    params.maxValue = gaugeGlobals.solarGaugeScaleMax;
                    params.titleString = strings.solar_title;
                    params.gaugeType = steelseries.GaugeType.TYPE4;
                    params.unitString = 'Wm\u207B\u00B2';
                    params.niceScale = false;
                    params.thresholdVisible = false;
                    params.lcdDecimals = 0;
                    if (config.showSunshineLed) {
                        params.userLedVisible = true;
                        params.userLedColor = steelseries.LedColor.YELLOW_LED;
                    }
                    ssGauge = new steelseries.Radial('canvas_solar', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_solar').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_solar').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    var tip, percent;
                    cache.value = +extractInteger(data.SolarRad);
                    cache.maxToday = extractInteger(data.SolarTM);
                    cache.currMaxValue = +extractInteger(data.CurrentSolarMax);
                    percent = (+cache.currMaxValue === 0 ? '--' : Math.round(+cache.value / +cache.currMaxValue * 100));
                    // Set a section (100 units wide) to show current theoretical max value
                    if (data.CurrentSolarMax !== 'N/A') {
                        ssGauge.setArea([steelseries.Section(cache.currMaxValue, Math.min(cache.currMaxValue + 100,
                                        gaugeGlobals.solarGaugeScaleMax), 'rgba(220,0,0,0.5)')]);
                    }
                    // Need to rescale the gauge?
                    cache.maxValue = Math.max(
                        Math.ceil(cache.value / 100) * 100,
                        Math.ceil(cache.currMaxValue / 100) * 100,
                        Math.ceil(cache.maxToday / 100) * 100,
                        gaugeGlobals.solarGaugeScaleMax);
                    if (cache.maxValue !== ssGauge.getMaxValue()) {
                        ssGauge.setMaxValue(cache.maxValue);
                    }
                    // Set the values
                    ssGauge.setMaxMeasuredValue(cache.maxToday);
                    ssGauge.setValueAnimated(cache.value);
                    if (config.showSunshineLed) {
                        ssGauge.setUserLedOnOff(percent !== '--' &&
                                                percent >= gaugeGlobals.sunshineThresholdPct &&
                                                +cache.value >= gaugeGlobals.sunshineThreshold);
                    }
                    if (ddimgtooltip.showTips) {
                        // update tooltip
                        tip = '' + strings.solar_title + ': ' + cache.value + ' W/m²';
                        if (typeof data.SolarTM !== 'undefined') {
                            tip += '
' + strings.solar_maxToday + ': ' + cache.maxToday + ' W/m²';
                        }
                        $('#imgtip9_txt').html(tip);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[9] !== null) {
                        $('#imgtip9_img').attr('src', config.imgPathURL + config.tipImgs[9] + cacheDefeat);
                    }
                }
                return {
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        singleCloudBase = (function () {
            var instance;   // Stores a reference to the Singleton
            var ssGauge;    // Stores a reference to the SS Gauge
            var cache = {};      // Stores various config values and parameters
            function init() {
                var params = $.extend(true, {}, commonParams);
                cache.sections = createCloudBaseSections(true);
                cache.value = 0.0001;
                cache.maxValue = gaugeGlobals.cloudScaleDefMaxm;
                // create Cloud base radial gauge
                if ($('#canvas_cloud').length) {
                    params.size = Math.ceil($('#canvas_cloud').width() * config.gaugeScaling);
                    params.section = cache.sections;
                    params.maxValue = cache.maxValue;
                    params.titleString = strings.cloudbase_title;
                    params.unitString = strings.metres;
                    params.thresholdVisible = false;
                    params.lcdDecimals = 0;
                    ssGauge = new steelseries.Radial('canvas_cloud', params);
                    ssGauge.setValue(cache.value);
                    // over-ride CSS applied size?
                    if (config.gaugeScaling !== 1) {
                        $('#canvas_cloud').css({width: params.size + 'px', height: params.size + 'px'});
                    }
                    // add a shadow to the gauge
                    if (config.showGaugeShadow) {
                        $('#canvas_cloud').css(gaugeShadow(params.size));
                    }
                    // subcribe to data updates
                    $.subscribe('gauges.dataUpdated', update);
                    $.subscribe('gauges.graphUpdate', updateGraph);
                } else {
                    // cannot draw gauge, return null
                    return null;
                }
                function update() {
                    cache.value = extractInteger(data.cloudbasevalue);
                    if (data.cloudbaseunit === 'm') {
                        // adjust metre gauge in jumps of 1000 metres, don't downscale during the session
                        cache.maxValue = Math.max(Math.ceil(cache.value / 1000) * 1000, gaugeGlobals.cloudScaleDefMaxm, cache.maxValue);
                        if (cache.value <= 1000 && config.roundCloudbaseVal) {
                            // and round the value to the nearest  10 m
                            cache.value = Math.round(cache.value / 10) * 10;
                        } else if (config.roundCloudbaseVal) {
                            // and round the value to the nearest 50 m
                            cache.value = Math.round(cache.value / 50) * 50;
                        }
                    } else {
                        // adjust feet gauge in jumps of 2000 ft, don't downscale during the session
                        cache.maxValue = Math.max(Math.ceil(cache.value / 2000) * 2000, gaugeGlobals.cloudScaleDefMaxft, cache.maxValue);
                        if (cache.value <= 2000 && config.roundCloudbaseVal) {
                            // and round the value to the nearest 50 ft
                            cache.value = Math.round(cache.value / 50) * 50;
                        } else if (config.roundCloudbaseVal) {
                            // and round the value to the nearest 100 ft
                            cache.value = Math.round(cache.value / 100) * 100;
                        }
                    }
                    if (cache.maxValue !== ssGauge.getMaxValue()) {
                        if (ssGauge.getMaxValue() > cache.maxValue) {
                            // Gauge currently showing more than our max (nice scale effct),
                            // so reset our max to match
                            cache.maxValue = ssGauge.getMaxValue();
                        } else {
                            // Gauge scale is too low, increase it.
                            // First set the pointer back to zero so we get a nice animation
                            ssGauge.setValue(0);
                            // and redraw the gauge with teh new scale
                            ssGauge.setMaxValue(cache.maxValue);
                        }
                    }
                    ssGauge.setValueAnimated(cache.value);
                    if (config.showPopupData) {
                        // static tooltip on cloud gauge
                        $('#imgtip11_txt').html('' + strings.cloudbase_popup_title + '
' + strings.cloudbase_popup_text);
                    }
                } // End of update()
                function updateGraph(evnt, cacheDefeat) {
                    if (config.tipImgs[11] !== null) {
                        $('#imgtip11_img').attr('src', config.imgPathURL + config.tipImgs[11] + cacheDefeat);
                    }
                }
                return {
                    data  : cache,
                    update: update,
                    gauge : ssGauge
                };
            } // End of init()
            return {
                // Get the Singleton instance if one exists
                // or create one if it doesn't
                getInstance: function () {
                    if (!instance) {
                        instance = init();
                    }
                    return instance;
                }
            };
        })(),
        getRealtime = function () {
            var url = config.realTimeURL;
            if ($.active > 0) {
                // kill any outstanding requests
                jqXHR.abort();
            }
            if (config.longPoll) {
                url += '?timestamp=' + timestamp;
            }
            jqXHR = $.ajax({url     : url,
                            cache   : (config.longPoll),
                            dataType: 'json',
                            timeout : config.longPoll ? (Math.min(config.realtimeInterval, 20) + 21) * 1000 : 21000 // 21 second time-out by default
                        }).done(function (data) {
                            checkRtResp(data);
                        }).fail(function (xhr, status, err) {
                            checkRtError(xhr, status, err);
                        });
        },
        //
        // checkRtResp() called by the Ajax fetch once data has been downloaded
        //
        checkRtResp = function (response) {
            var delay;
            statusTimer.reset(config.longPoll ? 1 : config.realtimeInterval);
            if (config.longPoll && response.status !== 'OK') {
                checkRtError(null, 'PHP Error', response.status);
            } else {
                if (processData(response)) {
                    delay = ajaxDelay;
                } else {
                    delay = 5;
                }
                if (delay > 0) {
                    downloadTimer = setTimeout(getRealtime, delay * 1000);
                } else {
                    getRealtime();
                }
            }
        },
        //
        // checkRtError() called by the Ajax fetch if an error occurs during the fetching realtimegauges.txt
        //
        checkRtError = function (xhr, status, error) {
            if (xhr == null || xhr.statusText !== 'abort') {
                clearTimeout(downloadTimer);
                ledIndicator.setLedOnOff(false);
                ledIndicator.setTitle(strings.led_title_unknown);
                statusScroller.setText(status + ': ' + error);
                downloadTimer = setTimeout(getRealtime, 5000);
            }
        },
        //
        // processData() massages the data returned in realtimegauges.txt, and posts a gauges.dataUpdated event to update the page
        //
        processData = function (dataObj) {
            var str, dt, tm, today, now, then, tmp, elapsedMins, retVal;
            if (config.longPoll) {
                timestamp = dataObj.timestamp;
                data = dataObj.data;
            } else {
                data = dataObj;
            }
            createSummary(data);
            if (typeof data.ver !== 'undefined' && data.ver >= realtimeVer) {
                try {
                    str = data.LastRainTipISO.split(' ');
                    dt = str[0].replace(/\//g, '-').split('-');
                    tm = str[1].split(':');
                    today = new Date();
                    today.setHours(0, 0, 0, 0);
                    if (typeof data.dateFormat === 'undefined') {
                        data.dateFormat = 'y/m/d';
                    } else {
                        data.dateFormat = data.dateFormat.replace('%', '');
                    }
                    if (data.dateFormat === 'y/m/d') {
                        then = new Date(dt[0], dt[1] - 1, dt[2], tm[0], tm[1], 0, 0);
                    } else if (data.dateFormat === 'd/m/y') {
                        then = new Date(dt[2], dt[1] - 1, dt[0], tm[0], tm[1], 0, 0);
                    } else {
                        then = new Date(dt[2], dt[0] - 1, dt[1], tm[0], tm[1], 0, 0);
                    }
                    if (then.getTime() >= today.getTime()) {
                        data.LastRained = strings.LastRainedT_info + ' ' + str[1];
                    } else if (then.getTime() + 86400000 >= today.getTime()) {
                        data.LastRained = strings.LastRainedY_info + ' ' + str[1];
                    } else {
                        data.LastRained = then.getDate().toString() + ' ' + strings.months[then.getMonth()] + ' ' + strings.at + ' ' + str[1];
                    }
                } catch (e) {
                    data.LastRained = data.LastRainTipISO;
                }
                if (data.tempunit.length > 1) {
                    data.tempunit = data.tempunit.replace(/&\S*;/, '°');
                } else {
                    data.tempunit = '°' + data.tempunit;
                }
                // Check for station off-line
                now = Date.now();
                tmp = data.timeUTC.split(',');
                sampleDate = Date.UTC(tmp[0], tmp[1] - 1, tmp[2], tmp[3], tmp[4], tmp[5]);
                if (now - sampleDate > config.stationTimeout * 60 * 1000) {
                    elapsedMins = Math.floor((now - sampleDate) / (1000 * 60));
                    ledIndicator.setLedColor(steelseries.LedColor.RED_LED);
                    ledIndicator.setTitle(strings.led_title_offline);
                    ledIndicator.blink(true);
                    if (elapsedMins < 120) {
                        tm = elapsedMins.toString() + ' ' + strings.StatusMinsAgo;
                    } else if (elapsedMins < 2 * 24 * 60) {
                        tm = Math.floor(elapsedMins / 60).toString() + ' ' + strings.StatusHoursAgo;
                    } else {
                        tm = Math.floor(elapsedMins / (60 * 24)).toString() + ' ' + strings.StatusDaysAgo;
                    }
                    data.forecast = strings.led_title_offline + ' ' + strings.StatusLastUpdate + ' ' + tm;
                } else if (+data.SensorContactLost === 1) {
                    ledIndicator.setLedColor(steelseries.LedColor.RED_LED);
                    ledIndicator.setTitle(strings.led_title_lost);
                    ledIndicator.blink(true);
                    data.forecast = strings.led_title_lost;
                } else {
                    ledIndicator.setLedColor(steelseries.LedColor.GREEN_LED);
                    ledIndicator.setTitle(strings.led_title_ok + '. ' + strings.StatusLastUpdate + ': ' + data.date);
                    ledIndicator.blink(false);
                    ledIndicator.setLedOnOff(true);
                    data.forecast = strings.StatusLastUpdate + ': ' + data.date;
                }
                data.forecast = $('