Full Code of kwindrem/SetupHelper for AI

main d9ded5decd3e cached
68 files
700.4 KB
185.9k tokens
607 symbols
1 requests
Download .txt
Showing preview only (729K chars total). Download the full file or copy to clipboard to get everything.
Repository: kwindrem/SetupHelper
Branch: main
Commit: d9ded5decd3e
Files: 68
Total size: 700.4 KB

Directory structure:
gitextract_d5l_5hwy/

├── .github/
│   └── workflows/
│       └── latest-tag.yml
├── FileSets/
│   ├── PatchSource/
│   │   ├── PageSettings.qml
│   │   ├── PageSettings.qml.orig
│   │   └── PageSettings.qml.patch
│   ├── VersionIndependent/
│   │   ├── MbDisplayDefaultPackage.qml
│   │   ├── MbDisplayPackageVersion.qml
│   │   ├── PageSettingsAddPackageList.qml
│   │   ├── PageSettingsPackageAdd.qml
│   │   ├── PageSettingsPackageEdit.qml
│   │   ├── PageSettingsPackageManager.qml
│   │   ├── PageSettingsPackageVersions.qml
│   │   ├── PageSettingsPmBackup.qml
│   │   └── PageSettingsPmInitialize.qml
│   ├── fileListPatched
│   └── fileListVersionIndependent
├── HelperResources/
│   ├── CommonResources
│   ├── DbusSettingsResources
│   ├── EssentialResources
│   ├── IncludeHelpers
│   ├── LogHandler
│   ├── ServiceResources
│   └── VersionResources
├── PackageDevelopmentGuidelines.md
├── PackageManager.py
├── ReadMe.md
├── blindInstall/
│   ├── SetupHelperVersion
│   ├── blindInstall.sh
│   ├── post-hook.sh
│   ├── pre-hook.sh
│   └── rcS.localForUninstall
├── changes
├── defaultPackageList
├── forSetupScript
├── genericSetupScript
├── gitHubInfo
├── makeVelib_python
├── patch
├── patchBookworm
├── rcS.local
├── reinstallMods
├── services/
│   └── PackageManager/
│       ├── log/
│       │   └── run
│       └── run
├── settingsList
├── setup
├── updatePackage
├── velib_python/
│   ├── dbusmonitor.py
│   ├── oldestVersion
│   ├── settingsdevice.py
│   ├── ve_utils.py
│   ├── vedbus.py
│   └── velib_python/
│       ├── latest/
│       │   ├── dbusmonitor.py
│       │   ├── oldestVersion
│       │   ├── settingsdevice.py
│       │   ├── ve_utils.py
│       │   └── vedbus.py
│       ├── v3.34/
│       │   ├── dbusmonitor.py
│       │   ├── oldestVersion
│       │   ├── settingsdevice.py
│       │   ├── ve_utils.py
│       │   └── vedbus.py
│       └── v3.41/
│           ├── dbusmonitor.py
│           ├── oldestVersion
│           ├── settingsdevice.py
│           ├── ve_utils.py
│           └── vedbus.py
├── venus-data-SetupHelperInstall.tgz
├── venus-data-UninstallAllPackages.tgz
└── version

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/latest-tag.yml
================================================
name: Add latest tag to new release
on:
  release:
    types: [published]
  workflow_dispatch:
  
jobs:
  run:
    name: Run local action
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@master

      - name: Run latest-tag
        uses: EndBug/latest-tag@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: FileSets/PatchSource/PageSettings.qml
================================================
import QtQuick 1.1
import com.victron.velib 1.0
import net.connman 0.1
import "utils.js" as Utils

MbPage {
	title: qsTr("Settings")
	property string bindPrefix: "com.victronenergy.settings"
	property VBusItem relay0Item: VBusItem {bind: "com.victronenergy.system/Relay/0/State"}
	property bool hasRelay0: relay0Item.valid

	model: VisibleItemModel {
		MbSubMenu {
			id: generalItem
			description: qsTr("General")
			subpage: Component {
				PageSettingsGeneral {
					title: generalItem.description
				}
			}
		}

		MbSubMenu {
			description: qsTr("Firmware")
			subpage: Component {
				PageSettingsFirmware {
					title: qsTr("Firmware")
				}
			}
		}

		MbSubMenu {
			description: qsTr("Date & Time")
			subpage: Component {
				PageTzInfo {
					title: qsTr("Date & Time")
				}
			}
		}

		MbSubMenu {
			description: qsTr("Remote Console")
			subpage: Component { PageSettingsRemoteConsole {} }
		}

		MbSubMenu {
			id: systemSetupItem
			description: qsTr("System setup")
			subpage: Component {
				PageSettingsSystem {
					title: systemSetupItem.description
				}
			}
		}

		MbSubMenu {
			id: dvcc
			description: qsTr("DVCC")
			subpage: Component {
				PageSettingsDVCC {
					title: dvcc.description
				}
			}
		}

		MbSubMenu {
			id: displayItem
			description: qsTr("Display & language")
			subpage: Component {
				PageSettingsDisplay {
					title: displayItem.description
				}
			}
		}

		MbSubMenu {
			id: vrmLoggerItem
			description: qsTr("VRM online portal")
			subpage: Component {
				PageSettingsLogger {
					title: vrmLoggerItem.description
				}
			}
		}

		MbSubMenu {
			VBusItem {
				id: systemType
				bind: "com.victronenergy.system/SystemType"
			}
			description: systemType.value === "Hub-4" ? systemType.value : qsTr("ESS")
			subpage: Component { PageSettingsHub4 {} }
		}

		MbSubMenu {
			description: qsTr("Energy meters")
			subpage: Component { PageSettingsCGwacsOverview {} }
		}

		MbSubMenu {
			description: qsTr("PV inverters")
			subpage: Component { PageSettingsFronius {} }
		}

		MbSubMenu {
			show: App.withQwacs
			description: qsTr("Wireless AC sensors")
			subpage: Component { PageSettingsQwacs {} }
		}

		MbSubMenu {
			description: qsTr("Modbus TCP/UDP devices")
			subpage: Component { PageSettingsModbus {} }
		}

		MbSubMenu {
			id: ethernetItem
			description: qsTr("Ethernet")
			subpage: Component { PageSettingsTcpIp { showLinkLocal: true } }
		}

		MbSubMenu {
			description: qsTr("Wi-Fi")
			property VeQuickItem accessPoint: VeQuickItem { uid: "dbus/com.victronenergy.platform/Services/AccessPoint/Enabled" }
			subpage: accessPoint.value !== undefined ? wifiWithAP : wifiWithoutAP
			Component { id: wifiWithoutAP; PageSettingsWifi {} }
			Component { id: wifiWithAP; PageSettingsWifiWithAccessPoint {} }
		}

		MbSubMenu {
			description: qsTr("GSM modem")
			subpage: Component { PageSettingsGsm {} }
		}

		MbSubMenu {
			description: qsTr("Bluetooth")
			subpage: Component { PageSettingsBluetooth {} }
			show: Connman.technologyList.indexOf("bluetooth") !== -1
		}

		MbSubMenu {
			description: qsTr("GPS")
			subpage: Component { PageSettingsGpsList {} }
		}

		MbSubMenu {
			description: qsTr("Generator start/stop")
			subpage: Component { PageRelayGenerator {} }
			show: hasRelay0
		}

		MbSubMenu {
			description: qsTr("Tank pump")
			subpage: Component { PageSettingsTankPump {} }
		}

		MbSubMenu {
			description: qsTr("Relay")
			subpage: Component { PageSettingsRelay {} }
			show: hasRelay0
		}

		MbSubMenu {
			description: qsTr("Services")
			subpage: Component { PageSettingsServices {} }
		}

		MbSubMenu {
			description: qsTr("I/O")
			subpage: ioSettings
			show: ioSettings.haveSubMenus
			PageSettingsIo { id: ioSettings }
		}

		/*
		MbSubMenu {
			description: qsTr("Backup & Restore")
			subpage: Component { PageSettingsBackup {} }
		}
		*/

		MbSubMenu {
			description: qsTr("Venus OS Large features")
			subpage: Component { PageSettingsLarge {} }
			property VBusItem signalK: VBusItem { bind: "com.victronenergy.platform/Services/SignalK/Enabled" }
			property VBusItem nodeRed: VBusItem { bind: "com.victronenergy.platform/Services/NodeRed/Mode" }
			show: signalK.valid || nodeRed.valid
		}

		MbSubMenu {
			description: "Debug"
			subpage: Component { PageDebug {} }
			showAccessLevel: User.AccessService
		}
//////// added for PackageManager
		MbSubMenu
		{
			description: qsTr("Package manager")
			subpage: Component { PageSettingsPackageManager {} }
		}
	}
}


================================================
FILE: FileSets/PatchSource/PageSettings.qml.orig
================================================
import QtQuick 1.1
import com.victron.velib 1.0
import net.connman 0.1
import "utils.js" as Utils

MbPage {
	title: qsTr("Settings")
	property string bindPrefix: "com.victronenergy.settings"
	property VBusItem relay0Item: VBusItem {bind: "com.victronenergy.system/Relay/0/State"}
	property bool hasRelay0: relay0Item.valid

	model: VisibleItemModel {
		MbSubMenu {
			id: generalItem
			description: qsTr("General")
			subpage: Component {
				PageSettingsGeneral {
					title: generalItem.description
				}
			}
		}

		MbSubMenu {
			description: qsTr("Firmware")
			subpage: Component {
				PageSettingsFirmware {
					title: qsTr("Firmware")
				}
			}
		}

		MbSubMenu {
			description: qsTr("Date & Time")
			subpage: Component {
				PageTzInfo {
					title: qsTr("Date & Time")
				}
			}
		}

		MbSubMenu {
			description: qsTr("Remote Console")
			subpage: Component { PageSettingsRemoteConsole {} }
		}

		MbSubMenu {
			id: systemSetupItem
			description: qsTr("System setup")
			subpage: Component {
				PageSettingsSystem {
					title: systemSetupItem.description
				}
			}
		}

		MbSubMenu {
			id: dvcc
			description: qsTr("DVCC")
			subpage: Component {
				PageSettingsDVCC {
					title: dvcc.description
				}
			}
		}

		MbSubMenu {
			id: displayItem
			description: qsTr("Display & language")
			subpage: Component {
				PageSettingsDisplay {
					title: displayItem.description
				}
			}
		}

		MbSubMenu {
			id: vrmLoggerItem
			description: qsTr("VRM online portal")
			subpage: Component {
				PageSettingsLogger {
					title: vrmLoggerItem.description
				}
			}
		}

		MbSubMenu {
			VBusItem {
				id: systemType
				bind: "com.victronenergy.system/SystemType"
			}
			description: systemType.value === "Hub-4" ? systemType.value : qsTr("ESS")
			subpage: Component { PageSettingsHub4 {} }
		}

		MbSubMenu {
			description: qsTr("Energy meters")
			subpage: Component { PageSettingsCGwacsOverview {} }
		}

		MbSubMenu {
			description: qsTr("PV inverters")
			subpage: Component { PageSettingsFronius {} }
		}

		MbSubMenu {
			show: App.withQwacs
			description: qsTr("Wireless AC sensors")
			subpage: Component { PageSettingsQwacs {} }
		}

		MbSubMenu {
			description: qsTr("Modbus TCP/UDP devices")
			subpage: Component { PageSettingsModbus {} }
		}

		MbSubMenu {
			id: ethernetItem
			description: qsTr("Ethernet")
			subpage: Component { PageSettingsTcpIp { showLinkLocal: true } }
		}

		MbSubMenu {
			description: qsTr("Wi-Fi")
			property VeQuickItem accessPoint: VeQuickItem { uid: "dbus/com.victronenergy.platform/Services/AccessPoint/Enabled" }
			subpage: accessPoint.value !== undefined ? wifiWithAP : wifiWithoutAP
			Component { id: wifiWithoutAP; PageSettingsWifi {} }
			Component { id: wifiWithAP; PageSettingsWifiWithAccessPoint {} }
		}

		MbSubMenu {
			description: qsTr("GSM modem")
			subpage: Component { PageSettingsGsm {} }
		}

		MbSubMenu {
			description: qsTr("Bluetooth")
			subpage: Component { PageSettingsBluetooth {} }
			show: Connman.technologyList.indexOf("bluetooth") !== -1
		}

		MbSubMenu {
			description: qsTr("GPS")
			subpage: Component { PageSettingsGpsList {} }
		}

		MbSubMenu {
			description: qsTr("Generator start/stop")
			subpage: Component { PageRelayGenerator {} }
			show: hasRelay0
		}

		MbSubMenu {
			description: qsTr("Tank pump")
			subpage: Component { PageSettingsTankPump {} }
		}

		MbSubMenu {
			description: qsTr("Relay")
			subpage: Component { PageSettingsRelay {} }
			show: hasRelay0
		}

		MbSubMenu {
			description: qsTr("Services")
			subpage: Component { PageSettingsServices {} }
		}

		MbSubMenu {
			description: qsTr("I/O")
			subpage: ioSettings
			show: ioSettings.haveSubMenus
			PageSettingsIo { id: ioSettings }
		}

		/*
		MbSubMenu {
			description: qsTr("Backup & Restore")
			subpage: Component { PageSettingsBackup {} }
		}
		*/

		MbSubMenu {
			description: qsTr("Venus OS Large features")
			subpage: Component { PageSettingsLarge {} }
			property VBusItem signalK: VBusItem { bind: "com.victronenergy.platform/Services/SignalK/Enabled" }
			property VBusItem nodeRed: VBusItem { bind: "com.victronenergy.platform/Services/NodeRed/Mode" }
			show: signalK.valid || nodeRed.valid
		}

		MbSubMenu {
			description: "Debug"
			subpage: Component { PageDebug {} }
			showAccessLevel: User.AccessService
		}
	}
}


================================================
FILE: FileSets/PatchSource/PageSettings.qml.patch
================================================
--- /Users/Kevin/GitHub/SetupHelper.copy/FileSets/PatchSource/PageSettings.qml.orig	2024-05-15 13:06:53
+++ /Users/Kevin/GitHub/SetupHelper.copy/FileSets/PatchSource/PageSettings.qml	2025-01-24 22:39:59
@@ -192,5 +192,11 @@
 			subpage: Component { PageDebug {} }
 			showAccessLevel: User.AccessService
 		}
+//////// added for PackageManager
+		MbSubMenu
+		{
+			description: qsTr("Package manager")
+			subpage: Component { PageSettingsPackageManager {} }
+		}
 	}
 }


================================================
FILE: FileSets/VersionIndependent/MbDisplayDefaultPackage.qml
================================================
//////// new for PackageManager

import QtQuick 1.1
import com.victron.velib 1.0
import "utils.js" as Utils

MbItem {
	id: root

	property int defaultIndex
	property string servicePrefix

    property bool isCurrentItem: root.ListView.isCurrentItem
	property MbStyle style: MbStyle { isCurrentItem: root.ListView.isCurrentItem }

    VBusItem { id: packageName; bind: getServiceBind ("PackageName") }


	onClicked: pageStack.push ("/opt/victronenergy/gui/qml/PageSettingsPackageAdd.qml", {defaultIndex: defaultIndex})


	function getServiceBind(param)
	{
		return Utils.path(servicePrefix, "/Default/", defaultIndex, "/", param)
	}


    MbRowSmall
    {
        description: ""

        anchors.verticalCenter: parent.verticalCenter
		Column
		{
			width: root.width - gitHubUser.width - gitHubBranch.width - 20
			Text // puts a bit of space above package name
			{
				text: " "
                font.pixelSize: 6
			}
			Text
			{
				text:packageName.valid ? packageName.value : ""
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
				font.pixelSize: 14
				horizontalAlignment: Text.AlignLeft
			}
			Text
			{
				text: ""
				font.pixelSize: 10
				horizontalAlignment: Text.AlignLeft
			}
		}
		Column
		{
			Text // puts a bit of space above version boxes
			{
				text: " "
                font.pixelSize: 3
			}
			Text
			{
				text: "GitHub User"
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
                font.pixelSize: 10
			}
			MbTextBlock
			{
				id: gitHubUser
				item { bind: getServiceBind("GitHubUser") }
				height: 20; width: 120
			}
			Text // puts a bit of space below version boxes - only needed in one column
			{
				text: " "
                font.pixelSize: 6
			}
        }
		Column
		{
			Text // puts a bit of space above version boxes
			{
				text: " "
                font.pixelSize: 3
			}
			Text
			{
				text: qsTr ("GitHub Tag")
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
                font.pixelSize: 10
			}
			MbTextBlock
			{
				id: gitHubBranch
				item { bind: getServiceBind("GitHubBranch") }
				height: 20; width: 120
			}
		}
    }
}


================================================
FILE: FileSets/VersionIndependent/MbDisplayPackageVersion.qml
================================================
//////// new for PackageManager

import QtQuick 1.1
import com.victron.velib 1.0
import "utils.js" as Utils

MbItem {
	id: root

	property int packageIndex
	property string servicePrefix
	property string settingsPrefix

    property bool isCurrentItem: root.ListView.isCurrentItem
	property MbStyle style: MbStyle { isCurrentItem: root.ListView.isCurrentItem }

	VBusItem { id: packageName; bind: getSettingsBind ("PackageName") }
    property VBusItem rebootNeededItem: VBusItem { bind: getServiceBind ( "RebootNeeded") }
    property VBusItem guiRestartNeededItem: VBusItem { bind: getServiceBind ( "GuiRestartNeeded") }
    property bool rebootNeeded: rebootNeededItem.valid && rebootNeededItem.value == 1
    property bool guiRestartNeeded: guiRestartNeededItem.valid && guiRestartNeededItem.value == 1

    VBusItem { id: incompatibleItem; bind: getServiceBind ( "Incompatible" ) }
    property string incompatibleReason: incompatibleItem.valid ? incompatibleItem.value : ""
    property bool compatible: incompatibleReason == ""
    VBusItem { id: platformItem; bind: Utils.path("com.victronenergy.packageManager", "/Platform" ) }
    property string platform: platformItem.valid ? platformItem.value : "???"

	// version info may be in platform service or in vePlatform.version
    VBusItem { id: osVersionItem; bind: Utils.path("com.victronenergy.platform", "/Firmware/Installed/Version" ) }
    property string osVersion: osVersionItem.valid ? osVersionItem.value : vePlatform.version

	onClicked: pageStack.push ("/opt/victronenergy/gui/qml/PageSettingsPackageEdit.qml", {newPackageIndex: packageIndex})


	function statusText ()
	{
		if (rebootNeeded)
			return qsTr ("REBOOT needed")
		if (guiRestartNeeded)
			return qsTr ("GUI restart needed")
		else if (incompatibleReason == 'PLATFORM')
			return qsTr ( "incompatible with " + platform )
		// don't show warning incompatibilities here - they are shown in the editor menu	
		else if (incompatibleReason != ""
				&& incompatibleReason.toLowerCase().indexOf ("warning") == -1 )
			return incompatibleReason
		else
			return ""
	}

	function getSettingsBind(param)
	{
		return Utils.path(settingsPrefix, "/", packageIndex, "/", param)
	}
	function getServiceBind(param)
	{
		return Utils.path(servicePrefix, "/Package/", packageIndex, "/", param)
	}

    MbRowSmall
    {
        description: ""

        anchors.verticalCenter: parent.verticalCenter
		Column
		{
			width: root.width - gitHubVersion.width - packageVersion.width - installedVersion.width - 20
			Text // puts a bit of space above package name
			{
				text: " "
                font.pixelSize: 6
			}
			Text
			{
				text:packageName.valid ? packageName.value : ""
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
				font.pixelSize: 14
				horizontalAlignment: Text.AlignLeft
			}
			Text
			{
				text: statusText ()
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
				font.pixelSize: 10
				horizontalAlignment: Text.AlignLeft
			}
		}
		Column
		{
			Text // puts a bit of space above version boxes
			{
				text: " "
                font.pixelSize: 3
			}
			Text
			{
				text: "GitHub"
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
                font.pixelSize: 10
			}
			MbTextBlock
			{
				id: gitHubVersion
				item { bind: getServiceBind("GitHubVersion") }
				height: 20; width: 99
			}
			Text // puts a bit of space below version boxes - only needed in one column
			{
				text: " "
                font.pixelSize: 6
			}
        }
		Column
		{
			Text // puts a bit of space above version boxes
			{
				text: " "
                font.pixelSize: 3
			}
			Text
			{
				text: qsTr ("Stored")
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
                font.pixelSize: 10
			}
			MbTextBlock
			{
				id: packageVersion
				item { bind: getServiceBind("PackageVersion") }
				height: 20; width: 99
			}
		}
		Column
		{
			Text // puts a bit of space above version boxes
			{
				text: " "
                font.pixelSize: 3
			}
			Text
			{
				text: qsTr ("Installed")
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
                font.pixelSize: 10
			}
			MbTextBlock
			{
				id: installedVersion
				item { bind: getServiceBind("InstalledVersion") }
				height: 20; width: 99
			}
		}
    }
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsAddPackageList.qml
================================================
/////// new menu for package version display

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: defaultCount.valid ? qsTr("Inactive packages (tap to activate)         ") : qsTr ("Package manager not running")

    property string servicePrefix: "com.victronenergy.packageManager"
	// use DefaultCount as an indication that PackageManager is running
    property VBusItem defaultCount: VBusItem { bind: Utils.path(servicePrefix, "/DefaultCount") }

    model: defaultCount.valid ? defaultCount.value : 0
    delegate: Component
    {
        MbDisplayDefaultPackage
        {
            servicePrefix: root.servicePrefix
            defaultIndex: index
        }
    }
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsPackageAdd.qml
================================================
/////// new menu for package add edit

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: editActionItem.valid ? qsTr("Add package") : qsTr ("Package manager not running")

    property bool isCurrentItem: root.ListView.isCurrentItem
	property MbStyle style: MbStyle { isCurrentItem: root.ListView.isCurrentItem }

    property string settingsPrefix: "com.victronenergy.settings/Settings/PackageManager"
    property string servicePrefix: "com.victronenergy.packageManager"
    property int defaultIndex:0
    property VBusItem defaultCount: VBusItem { bind: Utils.path(servicePrefix, "/DefaultCount") }
    property VBusItem editActionItem: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditAction") }
    property VBusItem editStatus: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditStatus") }
    property string packageName: packageNameBox.item.valid ? packageNameBox.item.value : ""
    property string editAction: editActionItem.valid ? editActionItem.value : ''

    property VBusItem defaultPackageName: VBusItem { bind: Utils.path ( servicePrefix, "/Default/", defaultIndex, "/", "PackageName" ) }
    property VBusItem defaultGitHubUser: VBusItem { bind: Utils.path ( servicePrefix, "/Default/", defaultIndex, "/", "GitHubUser" ) }
    property VBusItem defaultGitHubBranch: VBusItem { bind: Utils.path ( servicePrefix, "/Default/", defaultIndex, "/", "GitHubBranch" ) }
    property VBusItem editPackageName: VBusItem { bind: Utils.path ( settingsPrefix, "/Edit/", "PackageName" ) }
    property VBusItem editGitHubUser: VBusItem { bind: Utils.path ( settingsPrefix, "/Edit/", "GitHubUser" ) }
    property VBusItem editGitHubBranch: VBusItem { bind: Utils.path ( settingsPrefix, "/Edit/", "GitHubBranch" ) }
	property bool addPending: false
	property bool entryValid: editPackageName.value != "" && editGitHubUser.value != "" && editGitHubBranch.value != ""

	Component.onCompleted:
	{
		updateEdit ()
	}

	onEditActionChanged:
	{
		if (addPending && editAction == '')
		{
			addPending = false
			pageStack.pop()
		}
	}

	function getSettingsBind(param)
	{
		return Utils.path(settingsPrefix, "/Edit/", param)
	}
	function getServiceBind(param)
	{
		return Utils.path(servicePrefix, "/Default/", defaultIndex, "/", param)
	}

	// copy a set of default package values to Edit area when changing indexes
	function updateEdit ()
	{
		bindPrefix = Utils.path(servicePrefix, "/Default/", defaultIndex )
		var defaultName = defaultPackageName.valid ? defaultPackageName.value : "??"
		if (defaultName == "new")
			defaultName = ""
		editPackageName.setValue ( defaultName )
		editGitHubUser.setValue ( defaultGitHubUser.valid ? defaultGitHubUser.value : "??" )
		editGitHubBranch.setValue ( defaultGitHubBranch.valid ? defaultGitHubBranch.value : "??" )
		editStatus.setValue ("")
		editActionItem.setValue ("")
		addPending = false
	}

    function cancelEdit ()
    {
		addPending = false
		if (editAction == '')
			pageStack.pop()
		else
		{
			editStatus.setValue ("")
			editActionItem.setValue ("")
		}
	}
    function confirm ()
    {
		if (entryValid)
		{
			addPending = true
			// provide local confirmation of action - takes PackageManager too long
			editStatus.setValue ( "adding " + packageName)
			editActionItem.setValue ('add:' + packageName)
		}
    }
	model: VisibleItemModel
    {
        MbEditBox
        {
            id: packageNameBox
            description: qsTr ("Package name")
            maximumLength: 30
            item.bind: getSettingsBind ("PackageName")
            overwriteMode: false
            writeAccessLevel: User.AccessInstaller
        }
        MbEditBox
        {
            id: gitHubUserBox
            description: qsTr ("GitHub user")
            maximumLength: 20
            item.bind: getSettingsBind ("GitHubUser")
            overwriteMode: false
            writeAccessLevel: User.AccessInstaller
        }
        MbEditBox
        {
            id: gitHubBranchBox
            description: qsTr ("GitHub branch or tag")
            maximumLength: 20
            item.bind: getSettingsBind ("GitHubBranch")
            overwriteMode: false
            writeAccessLevel: User.AccessInstaller
        }
        MbOK
        {
            id: cancelButton
            width: 90
            anchors { right: parent.right  }
            description: ""
            value: editAction == '' ? qsTr("Cancel") : qsTr("OK")
            onClicked: cancelEdit ()
        }
        MbOK
        {
            id: proceedButton
            width: 100
            anchors { right: cancelButton.left; bottom: cancelButton.bottom }
            description: ""
            value: qsTr ("Proceed")
            onClicked: confirm ()
            show: editAction == '' && entryValid
            writeAccessLevel: User.AccessInstaller
        }
        Text
        {
            id: statusMessage
            width: 250
            wrapMode: Text.WordWrap
            anchors { left: parent.left; leftMargin: 10; bottom: cancelButton.bottom; bottomMargin: 5 }
            font.pixelSize: 12
            text:
            {
				if (editStatus.valid && editStatus.value != "")
					return editStatus.value
				else if (entryValid)
					return ("add " + packageName + " ?")
				else if (editPackageName.value == "")
					return ("enter a unique package name")
				else if (editGitHubUser.value == "")
					return ("enter GitHub user")
				else if (editGitHubBranch.value == "")
					return ("enter GitHub branch")
				else
					return ("")
			}
            color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
        }
    }
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsPackageEdit.qml
================================================
/////// new menu for package version edit

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: platform.valid ? qsTr("Package editor") : qsTr ("Package manager not running")

	property bool isCurrentItem: root.ListView.isCurrentItem
	property MbStyle style: MbStyle { isCurrentItem: root.ListView.isCurrentItem }

	property string settingsPrefix: "com.victronenergy.settings/Settings/PackageManager"
	property string servicePrefix: "com.victronenergy.packageManager"
	property int packageIndex: 0
	property int newPackageIndex:0
	property VBusItem packageCount: VBusItem { bind: Utils.path(settingsPrefix, "/Count") }
	property VBusItem editAction: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditAction") }
	property VBusItem editStatus: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditStatus") }
	property VBusItem packageNameItem: VBusItem { bind: getSettingsBind ("PackageName") }
	property string packageName: packageNameItem.valid ? packageNameItem.value : ""
	property bool isSetupHelper: packageName == "SetupHelper"

	property VBusItem incompatibleReasonItem: VBusItem { bind: getServiceBind ( "Incompatible" ) }
	property string incompatibleReason: incompatibleReasonItem.valid ? incompatibleReasonItem.value : ""
	property VBusItem incompatibleDetailsItem: VBusItem { bind: getServiceBind ( "IncompatibleDetails") }
	property string incompatibleDetails: incompatibleDetailsItem.valid ? incompatibleDetailsItem.value : ""
	property bool incompatible: incompatibleReason != ""
	property VBusItem platform: VBusItem { bind: Utils.path(servicePrefix, "/Platform") }
	property VBusItem incompatibleResolvableItem: VBusItem { bind: getServiceBind ( "IncompatibleResolvable") }
	
	property bool gitHubValid: gitHubVersion.item.valid && gitHubVersion.item.value.substring (0,1) === "v"
	property bool packageValid: packageVersion.item.valid && packageVersion.item.value.substring (0,1) === "v"
	property bool installedValid: installedVersion.item.valid && installedVersion.item.value.substring (0,1) === "v"
	property bool downloadOk: gitHubValid && gitHubVersion.item.value != ""
	property bool installOk: ! incompatible
	property string requestedAction: ''
	property bool actionPending: requestedAction != ''
	property bool waitForAction: editAction.value != '' && ! editError
	property bool editError: editAction.value == 'ERROR'
	property bool navigate: ! actionPending && ! waitForAction
	property bool detailsExist: incompatibleDetails != ""
	property bool detailsResolvable: incompatibleResolvableItem.valid ? incompatibleResolvableItem.value : ""

	property bool showDetails: false
	property string localError: ""

	// version info may be in platform service or in vePlatform.version
	VBusItem { id: osVersionItem; bind: Utils.path("com.victronenergy.platform", "/Firmware/Installed/Version" ) }
	property string osVersion: osVersionItem.valid ? osVersionItem.value : vePlatform.version

	// ActionNeeded is a global parameter provided inform the GUI that a GUI restart or system reboot is needed
	// when dismissed, a timer started which hides the information
	// when the timer expires, the information is shown again
	// changes to ActionNeeded will stop the timer so the new value will be shown immediately
	property VBusItem actionNeededItem: VBusItem { bind: Utils.path(servicePrefix, "/ActionNeeded") }
	property string actionNeeded: actionNeededItem.valid ? actionNeededItem.value : ""
	property bool showActionNeeded: ! hideActionNeededTimer.running && actionNeeded != ''

	
	onActionNeededChanged:
	{
		hideActionNeededTimer.stop ()
	}
	
	onWaitForActionChanged:
	{
		if ( ! waitForAction )
		{
			hideActionNeededTimer.stop ()
			requestedAction = ''
		}
	}

	onIncompatibleChanged:
	{
		if (! incompatible )
			showDetails = false
	}

	onActiveChanged:
	{
		if (active)
		{
			hideActionNeededTimer.stop ()
			resetPackageIndex ()
			refreshGitHubVersions ()
			acknowledgeError ()
			requestedAction = ''
		}
	}

	onNavigateChanged: resetPackageIndex ()
	
	// hide action for 10 minutes
	Timer
	{
		id: hideActionNeededTimer
		running: false
		repeat: false
		interval: 1000 * 60 * 10
	}

	// refresh the GitHub version GitHub version age is greater than 30 seconds
	property bool waitForIndexChange: false
	property bool waitForNameChange: false

	onPackageIndexChanged:
	{
		waitForIndexChange = false
	}
	onPackageNameChanged:
	{
		waitForNameChange = false
		refreshGitHubVersions ()
	}

	function refreshGitHubVersions ()
	{
		if ( waitForIndexChange || waitForNameChange )
			return
		else if (! active || editAction.value != "" || actionPending)
			return
		sendCommand ( 'gitHubScan' + ':' + packageName, false )
	}

	// acknowledge error reported from PackageManager
	//	and erase status message
	function acknowledgeError ()
	{
		if (editError)
		{
			editAction.setValue ("")
			editStatus.setValue ("")
		}
	}

	function resetPackageIndex ()
	{
		if (waitForAction)
			return

		if (newPackageIndex < 0)
			newPackageIndex = 0
		else if (newPackageIndex >= packageCount.value)
			newPackageIndex = packageCount.value - 1

		if (newPackageIndex != packageIndex)
		{
			waitForIndexChange = true
			waitForNameChange = true
			packageIndex = newPackageIndex
			requestedAction = ''
			showDetails = false
		}
	}

	function getSettingsBind(param)
	{
		return Utils.path(settingsPrefix, "/", packageIndex, "/", param)
	}
	function getServiceBind(param)
	{
		return Utils.path(servicePrefix, "/Package/", packageIndex, "/", param)
	}

	function sendCommand (command, updateEditStatus )
	{
		if (editAction.value != "")
			localError = "command could not be sent ("  + command + ")"
		else
		{
			if (updateEditStatus)
				editStatus.setValue ("sending " + command)
			editAction.setValue (command)
		}
	}

	// don't change packages if pending operation or waiting for completion
	function nextIndex ()
	{
		if (editError)
			return
		newPackageIndex += 1
		resetPackageIndex ()
	}
	function previousIndex ()
	{
		if (editError)
			return
		newPackageIndex -= 1
		resetPackageIndex ()
	}

	function cancelEdit ()
	{
		// cancel any pending operation
		requestedAction = ''
		showDetails = false

		acknowledgeError ()

		// if was showing action needed, hide that messge for now
		if (showActionNeeded)
			hideActionNeededTimer.start ()
	}
	function confirm ()
	{
		if (showDetails)
		{
			if (detailsResolvable)
			{
				sendCommand ( 'resolveConflicts:' + packageName, true )
				showDetails = false
			}
			// trigger setup script prechecks
			else
			{
				sendCommand ( 'check:' + packageName, true )
				showDetails = false
			}
		}
		else if (actionPending)
			sendCommand ( requestedAction + ':' + packageName, true )
		else if (showActionNeeded)
		{
			if (actionNeeded.indexOf ( "REBOOT" ) != -1 )
				sendCommand ( 'reboot', true )
			else if (actionNeeded.indexOf ( "restart" ) != -1 )
				sendCommand ( 'restartGui', true )
				hideActionNeededTimer.start ()
		}
		requestedAction = ''
	}
	function install ()
	{
		if (navigate && installOk && ! editError)
		{
			requestedAction = 'install'
			showDetails = false
		}
	}
	function uninstall ()
	{
		if (navigate && installedValid && ! editError)
		{
			requestedAction = 'uninstall'
			showDetails = false
		}
	}
	function gitHubDownload ()
	{
		if (navigate && downloadOk && ! editError)
		{
			requestedAction = 'download'
			showDetails = false
		}
	}
	function remove ()
	{
		if ( ! editError)
		{
			requestedAction = 'remove'
			showDetails = false
		}
	}

	model: VisibleItemModel
	{
		MbItemText
		{
			id: packageNameBox
			text: packageName + " versions"
		}
		Row
		{
			height: 25
			spacing: 1
			// leftPadding: 7; rightPadding: 5; can't use in earlier Qt versions
			// use dummy text field for left spacing instead
			Text
			{
				text: "  "
				font.pixelSize: 10
			}
			Text
			{
				text: "GitHub:"
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
				font.pixelSize: 10
			}
			MbTextBlock
			{
				id: gitHubVersion
				item { bind: getServiceBind("GitHubVersion") }
				height: 25; width: 112
			}
			Text
			{
				text: qsTr (" stored:")
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
				font.pixelSize: 10
			}
			MbTextBlock
			{
				id: packageVersion
				item { bind: getServiceBind("PackageVersion") }
				height: 25; width: 112
			}
			Text
			{
				text: qsTr (" installed:")
				color: isCurrentItem ? root.style.textColorSelected : root.style.textColor
				horizontalAlignment: Text.AlignRight
				font.pixelSize: 10
			}
			MbTextBlock
			{
				id: installedVersion
				item { bind: getServiceBind("InstalledVersion") }
				height: 25
				width: 112
			}
		}
		MbEditBox
		{
			id: gitHubUser
			description: qsTr ("GitHub user")
			maximumLength: 20
			item.bind: getSettingsBind ("GitHubUser")
			overwriteMode: false
			writeAccessLevel: User.AccessInstaller
		}
		MbEditBox
		{
			id: gitHubBranch
			description: qsTr ("GitHub branch or tag")
			maximumLength: 20
			item.bind: getSettingsBind ("GitHubBranch")
			overwriteMode: false
			writeAccessLevel: User.AccessInstaller
		}

		MbOK
		{
			id: cancelButton
			width: 85
			anchors { right: gitHubBranch.right; bottom: statusMessage.bottom }
			description: ""
			value: ( actionPending || showDetails ) ? qsTr("Cancel") : (editError ? qsTr("OK") : qsTr("Later"))
			onClicked: cancelEdit ()
			show: ( actionPending || showDetails || editError || showActionNeeded ) && ! waitForAction
		}
		MbOK
		{
			id: confirmButton
			width: 92
			anchors { right: cancelButton.left; bottom: statusMessage.bottom }
			description: ""
			value: ( actionPending || detailsResolvable ) ? qsTr("Proceed") : showDetails ? qsTr ("Recheck") : qsTr ("Now")
			onClicked: confirm ()
			show: ( actionPending || showDetails || showActionNeeded ) && ! waitForAction
			writeAccessLevel: User.AccessInstaller
		}
		MbOK
		{
			id: showDetailsButton
			width: 150
			anchors { right: gitHubBranch.right; bottom: statusMessage.bottom}
			description: ""
			value: qsTr("Show Details")
			onClicked: showDetails = true
			writeAccessLevel: User.AccessInstaller
			show: navigate && detailsExist && ! ( editError || actionPending || waitForAction || showActionNeeded || showDetails)
		}
		Text
		{
			id: statusMessage
			width:
			{
				var smWidth = root.width
				if (cancelButton.show)
					smWidth -= cancelButton.width
				if (confirmButton.show)
					smWidth -= confirmButton.width
				if (showDetailsButton.show)
					smWidth -= showDetailsButton.width
				return smWidth
			}
			height: Math.max (paintedHeight, 35)
			wrapMode: Text.WordWrap
			horizontalAlignment: Text.AlignLeft
			anchors { left: gitHubBranch.left; leftMargin: 5; top: gitHubBranch.bottom }
			font.pixelSize: 12
			color: isSetupHelper && requestedAction == 'uninstall' ? "red" : root.style.textColor
			text:
			{
				if (showDetails)
				{
					if (detailsResolvable)
						return ( incompatibleDetails + qsTr ("\nResolve conflicts?") )
					else
						return ( incompatibleDetails )
				}
				else if (actionPending)
				{
					if (isSetupHelper && requestedAction == 'uninstall')
						return qsTr ("WARNING: SetupHelper is required for these menus - uninstall anyway ?")
					else
						return (requestedAction + " " + packageName + " ?")
				}
				else if (editStatus.valid && editStatus.value != "")
					return ( editStatus.value )
				else if (showActionNeeded)
					return ( actionNeeded ) 
				else if (incompatible)
					return ( incompatibleReason )
				else
					return localError
			}
		}
		// bottom row of buttons
		MbOK
		{
			id: previousButton
			width: 100
			anchors { left: gitHubBranch.left; top: statusMessage.bottom; topMargin: 5  }
			description: ""
			value: qsTr("Previous")
			onClicked: previousIndex ()
			opacity: ! editError && newPackageIndex > 0 ? 1.0 : 0.2
		}
		MbOK
		{
			id: nextButton
			width: 70
			anchors { left: previousButton.right; top: statusMessage.bottom; topMargin: 5 }
			description: ""
			value: qsTr("Next")
			onClicked: nextIndex ()
			opacity: ! editError && (newPackageIndex < packageCount.value - 1) ? 1.0 : 0.2
		}
		MbOK
		{
			id: downloadButton
			width: 110
			anchors { right: installButton.left; top: statusMessage.bottom; topMargin: 5 }
			description: ""
			value: qsTr ("Download")
			onClicked: gitHubDownload ()
			opacity: ! editError && navigate && downloadOk > 0 ? 1.0 : 0.2
			writeAccessLevel: User.AccessInstaller
		}
		MbOK
		{
			id: installButton
			width: 80
			anchors { right: uninstallButton.left; top: statusMessage.bottom; topMargin: 5 }
			description: ""
			value: qsTr ("Install")
			onClicked: install ()
			opacity: ! editError && navigate && installOk > 0 ? 1.0 : 0.2
			writeAccessLevel: User.AccessInstaller
		}
		MbOK
		{
			id: uninstallButton
			width: 105
			anchors { right: gitHubBranch.right; top: statusMessage.bottom; topMargin: 5 }
			description: ""
			value: installedValid ? qsTr("Uninstall") : qsTr("Remove")
			onClicked: installedValid ? uninstall () : remove ()
			opacity: ! editError && navigate ? 1.0 : 0.2
			writeAccessLevel: User.AccessInstaller
		}
		// dummy item to allow scrolling to show last button line when status message has many lines
		MbItemText
		{
			text: ""
			opacity: 0
			show: statusMessage.height > 35
		}
	}
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsPackageManager.qml
================================================
/////// new menu for package version display

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: showControls ? qsTr("Package manager") : qsTr("Package manager not running")
    property string settingsPrefix: "com.victronenergy.settings/Settings/PackageManager"
    property string servicePrefix: "com.victronenergy.packageManager"
	property string bindVrmloggerPrefix: "com.victronenergy.logger"
    VBusItem { id: pmStatusItem; bind: Utils.path(servicePrefix, "/PmStatus") }
	property string pmStatus: pmStatusItem.valid ? pmStatusItem.value : ""
    VBusItem { id: mediaStatus; bind: Utils.path(servicePrefix, "/MediaUpdateStatus") }
    VBusItem { id: actionNeeded; bind: Utils.path(servicePrefix, "/ActionNeeded") }
    VBusItem { id: editAction; bind: Utils.path(servicePrefix, "/GuiEditAction") }
    property bool showMediaStatus: mediaStatus.valid && mediaStatus.value != ""
    property bool showControls: pmStatusItem.valid

	// the last status message received from PackageManager is saved in lastStatus
	//	so there is some status to display when PackageManager quits
	property string lastStatus: ""

	onPmStatusChanged:
	{
		if (pmStatusItem.valid)
			lastStatus = pmStatus
	}

	model: VisibleItemModel
    {
        MbItemText
        {
			id: status
            text:
            {
				if (mediaStatus.valid && mediaStatus.value != "")
					return mediaStatus.value
				else if (showControls)
					return pmStatusItem.value
				else
					return lastStatus
			}
            wrapMode: Text.WordWrap
            horizontalAlignment: Text.AlignHCenter
        }
        MbItemOptions
        {
            id: autoDownload
            description: qsTr ("GitHub check frequency")
            bind: Utils.path (settingsPrefix, "/GitHubAutoDownload")
            possibleValues:
            [
				MbOption { description: "Once"; value: 99 },
				MbOption { description: "Every 10 minutes"; value: 1 },
				MbOption { description: "Hourly"; value: 2 },
				MbOption { description: "Daily"; value: 3 },
                MbOption { description: "Never"; value: 0 }
            ]
            writeAccessLevel: User.AccessInstaller
        }
        MbSwitch
        {
            id: autoInstall
            bind: Utils.path (settingsPrefix, "/AutoInstall")
            name: qsTr ("Auto install packages")
            writeAccessLevel: User.AccessInstaller
        }
        MbSubMenu
        {
            description: qsTr("Active packages")
            subpage: Component { PageSettingsPackageVersions {} }
            show: showControls
        }
		MbSubMenu
        {
            description: qsTr("Inactive packages")
            subpage: Component { PageSettingsAddPackageList {} }
            show: showControls
        }
        MbOK
        {
            id: finishButton
            description:
            {
				if (editAction.value == 'reboot')
					return qsTr ("REBOOTING ...")
				else if (editAction.value == 'guiRestart')
					return qsTr ("restarting GUI ...")
				else
					return qsTr ("action to finish install/uninstall")
			}
            value:
             {
				if (! actionNeeded.valid)
					return ""
				else if (actionNeeded.value.indexOf ( "REBOOT" ) != -1 )
					return qsTr ("Reboot")
				else if (actionNeeded.value.indexOf ( "restart" ) != -1 )
					return qsTr ("Restart GUI")
				else
					return ""
			}
			onClicked:
            {
				if (finishButton.value == 'REBOOT')
				{
					// needs immediate update because GUI will be going down ASAP
					finishButton.description = qsTr ("REBOOTING ...")
					editAction.setValue ( 'reboot' )
				}
				else if (finishButton.value == 'guiRestart')
				{
					// needs immediate update because GUI will be going down ASAP
					finishButton.description = qsTr ("restarting GUI ...")
					editAction.setValue ( 'restartGui' )
				}
			}
            show: actionNeeded.valid && actionNeeded.value != ''
            writeAccessLevel: User.AccessInstaller
        }
		MbSubMenu
        {
            description: qsTr("Backup & restore settings")
            subpage: Component { PageSettingsPmBackup {} }
            show: showControls
        }
		MbOK {
			property int notMounted: 0
			property int mounted: 1
			property int unmountRequested: 2
			property int unmountBusy: 3

			function mountStateToText(s)
			{
				switch (s) {
				case mounted:
					return qsTr("Press to eject");
				case unmountRequested:
				case unmountBusy:
					return qsTr("Ejecting, please wait");
				default:
					return qsTr("No storage found");
				}
			}

			VBusItem {
				id: vMountState
				bind: Utils.path(bindVrmloggerPrefix, "/Storage/MountState")
			}
			description: qsTr("microSD / USB")
			value: mountStateToText(vMountState.value)
			writeAccessLevel: User.AccessUser
			onClicked: vMountState.setValue(unmountRequested);
			editable: vMountState.value === mounted
			cornerMark: false
		}
		MbSubMenu
        {
            description: qsTr("Restart or initialize ...")
            subpage: Component { PageSettingsPmInitialize {} }
            show: showControls
        }
    }
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsPackageVersions.qml
================================================
/////// new menu for package version display

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: defaultCount.valid ? qsTr("Active packages (tap to edit)        ") : qsTr ("Package manager not running")
    property string servicePrefix: "com.victronenergy.packageManager"
    property string settingsPrefix: "com.victronenergy.settings/Settings/PackageManager"
    property VBusItem count: VBusItem { bind: Utils.path(settingsPrefix, "/Count") }
	// use DefaultCount as an indication that PackageManager is running
    property VBusItem defaultCount: VBusItem { bind: Utils.path(servicePrefix, "/DefaultCount") }
	property VBusItem editAction: VBusItem { bind: Utils.path(servicePrefix, "/GuiEditAction") }

	// notify PackageManager to refresh GitHub versions for all packages
	// when this menu goes active (entering from parent or returning from child)
	// or if first package's GitHub version age is greater than 60 seconds
	onActiveChanged: refreshGitHubVersions ()

	function refreshGitHubVersions ()
	{
		if (! active)
			return
		else if ( editAction.value != "" )
			return

		editAction.setValue ('gitHubScan:ALL')
	}


	model: defaultCount.valid ? count.valid ? count.value : 0 : 0
    delegate: Component
    {
        MbDisplayPackageVersion
        {
            servicePrefix: root.servicePrefix
            settingsPrefix: root.settingsPrefix
            packageIndex: index
        }
    }
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsPmBackup.qml
================================================
/////// new menu for settings backup and restore

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: qsTr("Settings backup & restore")
    property string settingsPrefix: "com.victronenergy.settings/Settings/PackageManager"
    property string servicePrefix: "com.victronenergy.packageManager"
	VBusItem { id: mediaAvailable; bind: Utils.path(servicePrefix, "/BackupMediaAvailable") }
	VBusItem { id: settingsFileExists; bind: Utils.path(servicePrefix, "/BackupSettingsFileExist") }
	VBusItem { id: settingsLocalFileExists; bind: Utils.path(servicePrefix, "/BackupSettingsLocalFileExist") }
	VBusItem { id: backupProgressItem; bind: Utils.path(servicePrefix, "/BackupProgress") }
	property int backupProgress: backupProgressItem.valid ? backupProgressItem.value : 0

	model: VisibleItemModel
    {
        MbItemText
        {
			id: info
            text: qsTr ("Backup and restore\nSOME system settings, logs and logos\nthis is NOT the Victron mechanism\ncurrently under development")
            wrapMode: Text.WordWrap
            horizontalAlignment: Text.AlignHCenter
        }
        MbItemText
        {
			id: status
            text:
			{
				if (backupProgress == 21 || backupProgress == 23)
					return qsTr ("backing up settings to local storage ... (may take a while)")
				else if (backupProgress == 22 || backupProgress == 24)
					return qsTr ("restoring settings from local storage ... (may take a while)")
				else if (backupProgress == 1 || backupProgress == 3)
					return qsTr ("backing up settings ... (may take a while)")
				else if (backupProgress == 2 || backupProgress == 4)
					return qsTr ("restoring settings ... (may take a while)")
				else if ( ! mediaAvailable.valid || mediaAvailable.value == 0)
					return qsTr ("No USB or SD media found - insert one to continue")
				else if (settingsFileExists.valid && settingsFileExists.value == 1)
					return qsTr ("Settings backup file found")
				else
					return ""
			}
            wrapMode: Text.WordWrap
            horizontalAlignment: Text.AlignHCenter
        }
		MbOK
		{
			description: qsTr("Backup settings, logos, logs")
			value: qsTr("Press to backup")
			onClicked: backupProgressItem.setValue (1)
			show: mediaAvailable.valid && mediaAvailable.value == 1 && backupProgressItem.value == 0
            writeAccessLevel: User.AccessInstaller
		}
		MbOK
		{
			description: qsTr("Restore settings, logos")
			value: qsTr("Press to restore")
			onClicked: backupProgressItem.setValue (2)
			show: settingsFileExists.valid && settingsFileExists.value == 1 && backupProgressItem.value == 0
            writeAccessLevel: User.AccessInstaller
		}
		MbOK
		{
			description: qsTr("Backup settings to local storage")
			value: qsTr("Press to backup")
			onClicked: backupProgressItem.setValue (21)
			show: backupProgressItem.value == 0
            writeAccessLevel: User.AccessInstaller
		}
		MbOK
		{
			description: qsTr("Restore settings to from storage")
			value: qsTr("Press to restore")
			onClicked: backupProgressItem.setValue (22)
			show: settingsLocalFileExists.valid && settingsLocalFileExists.value == 1 && backupProgressItem.value == 0
            writeAccessLevel: User.AccessInstaller
		}
    }
}


================================================
FILE: FileSets/VersionIndependent/PageSettingsPmInitialize.qml
================================================
/////// new menu for PackageManager initialize

import QtQuick 1.1
import "utils.js" as Utils
import com.victron.velib 1.0

MbPage {
	id: root
	title: pmRunning ? qsTr("PackageManager restart/initialize") : qsTr ("Package manager not running")
    property string settingsPrefix: "com.victronenergy.settings/Settings/PackageManager"
	VBusItem { id: pmStatus; bind: Utils.path(servicePrefix, "/PmStatus") }
	property bool pmRunning: pmStatus.valid

	property bool showInProgress: false
	property string initializeMessage: ""

	onPmRunningChanged: { showInProgress = false }

    function sendCommand (command, message)
    {
		initializeMessage = message
		showInProgress = true
		editAction.setValue (command)
    }

	model: VisibleItemModel
    {
		MbOK
		{
			description: qsTr("Restart")
			value: qsTr("Press to restart Package Manager")
			onClicked:sendCommand ("RESTART_PM", qsTr ("restarting Package Manager ..."))
			writeAccessLevel: User.AccessInstaller
			show: ! showInProgress
		}
		MbOK
		{
			description: qsTr("Restart GUI")
			value: qsTr("Press to restart GUI")
			onClicked:sendCommand ("restartGui", qsTr ("restarting GUI ..."))
			writeAccessLevel: User.AccessInstaller
			show: ! showInProgress
		}
        MbItemText
        {
			id: info
            text: qsTr ("Initializing PackageManager will\nreset persistent storage to an empty state\nGit Hub user and branch are reset to defaults\nPackages added manually must be added again")
            wrapMode: Text.WordWrap
            horizontalAlignment: Text.AlignHCenter
			show: ! showInProgress
        }
		MbOK
		{
			description: qsTr("Initialize")
			value: qsTr("Press to INITIALIZE Package Manager")
			onClicked: sendCommand ("INITIALIZE_PM", qsTr ("INITIALIZING Package Manager ..."))
            writeAccessLevel: User.AccessInstaller
            show: ! showInProgress
		}
        MbItemText
        {
			id: initializingMessage
            text: initializeMessage
            wrapMode: Text.WordWrap
            horizontalAlignment: Text.AlignHCenter
            show: showInProgress
        }
    }
}


================================================
FILE: FileSets/fileListPatched
================================================
/opt/victronenergy/gui/qml/PageSettings.qml


================================================
FILE: FileSets/fileListVersionIndependent
================================================
/opt/victronenergy/gui/qml/PageSettingsPackageManager.qml
/opt/victronenergy/gui/qml/PageSettingsPackageVersions.qml
/opt/victronenergy/gui/qml/PageSettingsPackageEdit.qml
/opt/victronenergy/gui/qml/PageSettingsAddPackageList.qml
/opt/victronenergy/gui/qml/PageSettingsPackageAdd.qml
/opt/victronenergy/gui/qml/PageSettingsPmBackup.qml
/opt/victronenergy/gui/qml/PageSettingsPmInitialize.qml
/opt/victronenergy/gui/qml/MbDisplayPackageVersion.qml
/opt/victronenergy/gui/qml/MbDisplayDefaultPackage.qml


================================================
FILE: HelperResources/CommonResources
================================================
#!/bin/bash


# CommonResources for SetupHelper
# contains a functions and variables necessary for a setup script to interface with reinstallMods
#
# Refer to the SetupHelper ReadMe file for details on how to use these resources.

# what action the script should take:
#  NONE - do noting - signals script to prompt for user input on how to proceed
#  INSTALL - install package components
#  (decommissioned) PROMPT - prompt user for additional installation options
#  UNINSTALL - remove package components
#  EXIT - exit script without taking any action
#  CHECK - runs file set checks only
#		this will attempt to create a missing file set so PackageManager
#		won't report it missing
# CommonResources may set the the action if initial checks
#  indicate a clear direction
# otherwise, the action will be set based on user input (in the script)
# if failures occur during installation,
# scriptAction should be changed to UNINSTALL so the installation can be cleaned up
# and the setup script should test for UNINSTALL after it attempts installation
# A file set error indicates the file set for the current verion is not usable
#  and installation should not occur
#  checkFileSets EXITS locally

scriptAction='NONE'

# flags to control setup script behavior (in endScript)
rebootNeeded=false
runAgain=false
filesUpdated=false
restartGui=false
restartGeneratorService=false
restartSystemCalc=false
restartDigitalinputs=false

# file lists are populated by getFileLists called from_chckFileSets and autoinstall
#	so these are global
fileList=()
fileListVersionIndependent=()
fileListAll=()


######## skip to bottom of file for remainder of code executed when script is sourced


# cleanup on any exit
exitCleanup ()
{
	# remove temp directory
	rm -rf "$tempFileDir"
}

trap exitCleanup EXIT

# checkPackageDependencies checks the packageDependencies file in the package directory
#
# all unmet dependencies are reported to the command line/log
#	and to stderr if not running from the command line
# then the script exists

function checkPackageDependencies ()
{
	dependencyFile="$scriptDir/packageDependencies"

	#no dependencies specified for this package
	if ! [ -f "$dependencyFile" ]; then return; fi

	errors=""
	while IFS= read -r line; do
		error=""
		read package requirement <<< "$line"
		if [ -f "$installedVersionPrefix"$package ]; then
			packageInstalled=true
		else
			packageInstalled=false
		fi
		case $requirement in
			installed)
				if ! $packageInstalled ; then
					error="$package must be installed"
				fi
				;;
			uninstalled)
				if $packageInstalled ; then
					error="$package must be uninstalled"
				fi
				;;
		esac
		if ! [ -z "$error" ]; then
			if ! [ -z "$errors" ]; then
				errors+=", "
			fi
			errors+="$error"
		fi

	done < "$dependencyFile"
	if ! [ -z "$errors" ]; then
		setInstallFailed $EXIT_PACKAGE_CONFLICT "$errors"
	fi
}

# getFileLists reads the file list from files in the FileSets directory
#
#	'fileList' file must only list version-dependent files
#	'fileListVersionIndependent' file must list only version-independent files
#		prior to SetupHelper v6.0, this list is ignored
#	'fileListPatched' lists all files that should be patched before installation
#
# $1 specifies where the path to the fileList files
#
# three composite file lists are returned in global arrays:
#	fileList contains only version-dependent files
#	fileListVersionIndependent contains only version-independent files
#	fileListPatched contains only files that need to be patched
#	fileListAll contains both versioned and version-independent files

function getFileLists ()
{
	local verListFile="$1/fileList"
	local indListFile="$1/fileListVersionIndependent"
	local patchListFile="$1/fileListPatched"
	local tempListVer=()
	local tempListInd=()
	local tempListPatched=()

	if [ -f "$verListFile" ]; then
		while read -r line || [[ -n "$line" ]]; do
			read -a params <<< $line
			# parse line into space-separted parameters then discard any that don't begin with /
			# this strips all comments beginning with # as well as any leading or trailing spaces
			for param in ${params[@]} ; do
				case $param in
					/*)
						tempListVer+=("$param")
						;;
				esac
			done
		done < "$verListFile"
	fi
	if [ -f "$indListFile" ]; then
		while read -r line || [[ -n "$line" ]]; do
			read -a params <<< $line
			for param in ${params[@]} ; do
				case $param in
					/*)
						tempListInd+=("$param")
						;;
				esac
			done
		done < "$indListFile"
	fi
	if [ -f "$patchListFile" ]; then
		while read -r line || [[ -n "$line" ]]; do
			read -a params <<< $line
			for param in ${params[@]} ; do
				case $param in
					/*)
						tempListPatched+=("$param")
						;;
				esac
			done
		done < "$patchListFile"
	fi

	# remove duplicate files from each list
	fileList=($(printf "%s\n" "${tempListVer[@]}" | sort -u))
	fileListVersionIndependent=($(printf "%s\n" "${tempListInd[@]}" | sort -u))
	fileListPatched=($(printf "%s\n" "${tempListPatched[@]}" | sort -u))
	tempListAll=(${fileList[@]})
	tempListAll+=(${fileListVersionIndependent[@]})
	tempListAll+=(${fileListPatched[@]})
	fileListAll=($(printf "%s\n" "${tempListAll[@]}" | sort -u))
}


# yesNoPrompt provides user prompting requesting a yes/no response
#
# $1 is the prompt displayed when pausing for user input
#
# $yesResponse is set to true if the response was yes
#
# returns 0 for yes, 1 for no

yesNoPrompt ()
{
    response=''
    while true; do
        /bin/echo -n "$*"
        read response
        case $response in
            [yY]*)
                yesResponse=true
                return 0
                break
                ;;
            [nN]*)
                yesResponse=false
                return 1
                break
                ;;
            *)
        esac
    done
}


# standardActionPrompt provides the standard set of options for selecting script's action
# scriptAction is set by install/uninstall
# other actions are handled locally, including quitting from the script
#
# if nonstandard prompts are necessary, duplicate this code in the setup script
# and add the additional options and do not call standardActionPrompt
#
# the reinstall option is permitted only if setup options were previously set
# if the the reinstall action is choosen, the script action is set to INSTALL
# the setup script can then test this to skip further prompts
#
# $1 indicates if there are additional prompts needed during installaiton
# if this parameter is 'MORE_PROMPTS', installaiton does NOT change scriptAction
# if this parameter does not exist, installation WILL change scriptAction to INSTALL
# this provides backaward compatibility with scripts written prior to the reinstall logic
#
standardActionPrompt ()
{
    if [ $# -gt 0 ] && [ $1 == 'MORE_PROMPTS' ]; then
        updateScriptAction=false
    else
        updateScriptAction=true
    fi

    echo
    echo "Available actions:"
	# don't allow install choice if incompatibilities have already been detected
	if ! $installFailed ; then
		echo "  Install and activate (i)"
	else
		echo "  can't install - errors reported above"
	fi
    if $optionsSet ; then
        echo "  Reinstall (r) based on options provided at last install"
    fi
    echo "  Uninstall (u) and restores all files to stock"
    echo "  Quit (q) without further action"
    echo "  Display log (l) outputs the last 100 lines of the log"
    echo
    response=''
    while true; do
        /bin/echo -n "Choose an action from the list above: "
        read response
        case $response in
            [iI]*)
				if ! $installFailed ; then
					if $updateScriptAction ; then
						scriptAction='INSTALL'
					fi
					break
				fi
                ;;
            [rR]*)
                if $optionsSet ; then
                    scriptAction='INSTALL'
                    break
                fi
                ;;
            [uU]*)
                scriptAction='UNINSTALL'
                break
                ;;
            [qQ]*)
                exit $EXIT_SUCCESS
                ;;
            [lL]*)
				tail -100 "$logFile" | tai64nlocal
                ;;
            *)
        esac
    done
}


# forcePackageUninstall insures a conflicting package is uninstalled before
#	this package is installed
# the setup script must call this script BEFORE it begins installing anything
# $1 is the package name
# $2 contains an optional message

forcePackageUninstall ()
{
	if (( $# < 1 )); then
		return
	fi
	if [ -f "$installedVersionPrefix""$1" ]; then
		if (( $# >= 2 )); then
			logMessage "${@:2}"
		else
			logMessage "uninstalling $1 - it conflicts with $packageName"
		fi
		if [ -e "/data/$1/setup" ]; then
			"/data/$1/setup" "uninstall" "auto" "deferReboot" "deferGuiRestart"
		else
			logMessage "WARNING can't uninstall $1 - no package directory or no setup script"
		fi
		if [ -e "/data/settupOptions/$1" ]; then
			touch "/data/settupOptions/$1/DO_NOT_AUTO_INSTALL"
		fi
	fi
}


# backupActiveFile makes a copy of the active file in file.orig
# if the original file does not exist the NO_ORIG flag is set
#	to allow restoreAciveFile to remove the active file
#
# if the backup (.orig file) exists the backup is not updated
#
# $1 is the full path/file name to be backed up
#
# returns 0 if backup was made, 1 if not
#

backupActiveFile ()
{
	# don't do any work if install has already failed
	if $installFailed ; then
		return 1
	fi
	local activeFile="$1"
	local origFile="$activeFile.orig"
	local noOrigFile="$activeFile.NO_ORIG"

	if [ -e "$activeFile" ]; then
		if ! [ -e "$origFile" ]; then
			cp "$activeFile" "$origFile"
			rm -f "$noOrigFile"
			return 0
		else
			return 1
		fi
	else
		if ! [ -e "$noOrigFile" ]; then
			touch "$noOrigFile"
			return 0
		else
			return 1
		fi		
	fi
}


# SetupHelper maintains a set of "restart flags"
#	that control service restarts and system reboot
#	after the package has been installed / uninstalled
#
# some restarts/reboots are based on the directory of the modified file
# others are based on the actual file itself
#
# not all services or specific files are flagged so some work in the setup script may be needed
#
# $1 is the full path and name to the modified file

updateRestartFlags ()
{
	# flag indicating any file update occurred
	filesUpdated=true

	case $1 in
		/opt/victronenergy/gui*)
			restartGui=true
			return;;
		#### TODO: add gui-v2
		/opt/victronenergy/dbus-generator*/*)
			restartGeneratorService=true
			return;;
		/opt/victronenergy/dbus-systemcalc-py/*)
			restartSystemCalc=true
			return;;
		/opt/victronenergy/dbus-digitalinputs/*)
			restartDigitalinputs=true
			return;;

		/u-boot/overlay./*) # Raspberry PI DT overlay
			rebootNeeded=true
			return;;
		/etc/udev/rules.d/*) # udev rules directory
			rebootNeeded=true
			return;;
	esac

	# reboots based on specific file
	case $( basename $1 ) in
		gpio_list)
			rebootNeeded=true
			return;;
		config.txt)
			rebootNeeded=true
			return;;
	esac
}


# updateActiveFile first backs up the active file
# then copies the replacement (aka source) to the active file location (aka destination)
#
# two variations:
#
# updateActiveFile activeFile
#   an attempt is made to locate the source (replacement)
#   in the version directory or FileSets
#
# updateActiveFile sourceFile activeFile
#   a separate source (replacement) file is specified
#
# both sourceFile and activeFile must be a full path to the file
#
# if the update fails, scriptAction is changed to UNINSTALL
#
# global thisFileUpdated is set to true if file was updated, false if not
#	thisFileUpdated supports the old mechanism which may be used in some setup scripts
# returns 0 if file was updated, 1 if not


updateActiveFile ()
{
    thisFileUpdated=false

	# don't do any work if install has already failed
	if $installFailed ; then
		return 1
	fi

    local sourceFile=""
    local activeFile=""

    # separate source and replacement files specified
    if [ $# == 2 ]; then
        if [ -f "$1" ]; then
			sourceFile="$1"
        else
            setInstallFailed $EXIT_FILE_SET_ERROR "specified soure file "$1" does not exist"
			return 1
        fi
        activeFile="$2"
    # use active file for both source and destination
    else
        activeFile="$1"
    fi
	local baseName=$(basename "$activeFile")

	# replacement files are not needed for some versions
	# if so marked, leave original untouched
	if [ -e "$fileSet/$baseName.USE_ORIGINAL" ]; then
		return 1
	fi

	# the location of the active file must exist
	if [ ! -e $(dirname "$activeFile") ]; then
		logMessage "path to $activeFile does not exist - skiping update"
		return 1
	fi

    local usePatchedFile=false
	local patchedReplacement="$tempFileDir/$baseName.patchedForInstall"
	local currentPatchFile="$tempFileDir/$baseName.currentPatch"

	# source file not specified separately - look for it in expected places
    if [ -z "$sourceFile" ]; then
		# first in temp files - patched file
 		if [ -e "$patchedReplacement" ] && [ -e "$currentPatchFile" ]; then
            sourceFile="$patchedReplacement"
            usePatchedFile=true
		# then in temp files - replacement
		elif [ -e "$tempFileDir/$baseName" ]; then
            sourceFile="$tempFileDir/$baseName"
		# then in version-specific FileSet
        elif [ -e "$fileSet/$baseName" ]; then
            sourceFile="$fileSet/$baseName"
		# then in version-independent file set
        elif [ -e "$versionIndependentFileSet/$baseName" ]; then
            sourceFile="$versionIndependentFileSet/$baseName"
		# then in FileSets (previous location of version-indepencent files)
        elif [ -e "$pkgFileSets/$baseName" ]; then
            sourceFile="$pkgFileSets/$baseName"
		else
			# if directory for file exists but no sourceFile - can't continue
			if ! [ -e $( dirname "$activeFile" ) ]; then
				logMessage "enclosing directory for $activeFile not found - skipping update"
			elif ! [ -e "$activeFile" ]; then
				logMessage "no replacement or active file $activeFile - skipping update"
			else
				setInstallFailed $EXIT_FILE_SET_ERROR "no soure file for $activeFile"
			fi
			return 1
        fi
    fi

	# can't continue if other packages modified this file and this is a replacement (not a patch)
	local local packageList="$activeFile.package"
    local previousPackage=""
    local matchFound=false
	if [ -e "$packageList" ]; then
		previousPackages=$( cat "$packageList" )
		for previousPackage in ${previousPackages[@]}; do
			if [ "$packageName" == "$previousPackage" ]; then
				matchFound=true
			elif ! $usePatchedFile ; then
				setInstallFailed $EXIT_PACKAGE_CONFLICT "$baseName was already modfied by $previousPackage"
				return 1
			fi
		done
	fi

	# add file to installed files list (used by uninstallAll)
	if ! [ -e "$installedFilesList" ] \
			|| (( $( grep -c "$activeFile" "$installedFilesList" ) == 0 )); then
		echo "$activeFile" >> "$installedFilesList"
	fi

	# save the current patch file for use during a future uninstall
	local previousPatchFile="$previousPatchesDir/$baseName.patch"
	if $usePatchedFile ; then
		cp "$currentPatchFile" "$previousPatchFile"
	# no patch file used for this update
	else
		rm -f "$previousPatchFile"
	fi

	# if replacement, replace the .package file
	if ! $usePatchedFile ; then
		echo $packageName > "$packageList"
	# if patch and this package not in list yet, add add it
	elif ! $matchFound ; then
		echo $packageName >> "$packageList"
	fi

	# update the active file if needed
	# patched files have already incorporated current active file content
	# 	so nothing diffrerent here
	if ! [ -f "$activeFile" ] || ! cmp -s "$sourceFile" "$activeFile" ; then
		backupActiveFile "$activeFile"
		cp "$sourceFile" "$activeFile"
		updateRestartFlags "$activeFile"
		thisFileUpdated=true
	fi

	# insure active file has proper permissions
	if [ -f "$activeFile" ]; then
		chmod +r "$activeFile"
		if [[ -x "$activeFile.orig" ]] || [[ -x "$sourceFile" ]] ; then
			chmod +x "$activeFile"
		fi
	fi

	if $thisFileUpdated; then
		return 0
	else
		return 1
	fi
} # end updateActiveFile ()


# restoreActiveFile 
# restores the active file to the content before this package modified it
# for replacements, the backup copy is moved to the active location
# if the backup copy doesn't exist BUT the NO_ORIG flag is set
# the active copy is deleted to restore the system to stock
#
# for patches, this package's changes are removed from the active file
#	by reverse patching it
#
# if the reverse patch fails, the original is restored
#	modifications from all other packages are also removed !!
#	this is drastic but has best chance to leave the system without errors
#
# $1 is the active name, the one to be backed up
#
# returns 0 if active file was restored, 1 if not
# also sets thisFileUpdated for backwards compatibility with existing setup scripts
#

restoreActiveFile ()
{
    thisFileUpdated=false
	local activeFile="$1"
	local packageList="$activeFile.package"
	# look for this package's name in .package list
	#	and remove it if found
	matchFound=false
	remainingPackages=""
	if [ -f "$packageList" ]; then
		previousPackages=($( cat "$packageList" ))
		for previousPackage in ${previousPackages[@]}; do
			if [ "$packageName" == "$previousPackage" ]; then
				matchFound=true
			else
				remainingPackages+="$previousPackage "
			fi
		done
	# no .package file - so proceed with removal anyway
	else
		matchFound=true
	fi
	
	# if this package not found - nothing to do
	if ! $matchFound ; then
		return 1
    fi

	local baseName=$( basename "$activeFile" )
	local previousPatchFile="$previousPatchesDir/$baseName.patch"

	reversePatchError=false
	restoreOriginal=false
	# no other packages so restore the original
	if [ -z "$remainingPackages" ] ; then
		restoreOriginal=true
	# other packages have also modified the active file
	# attempt to reverse patch the active file
	#	if success, move result into the active position
	#	DO NOT restore original
	else
		if [ -e "$previousPatchFile" ]; then
			tempFile="$tempFileDir/$baseName"
			cp "$activeFile" "$tempFile"
			if $patch --reverse -o "$activeFile.tmp" "$tempFile" "$previousPatchFile" &> /dev/null ; then
				mv -f "$activeFile.tmp" "$activeFile"
				thisFileUpdated=true
			else
				logMessage "CRITICAL: $packageName $baseName - reverse patch failed"
				rm -f "$activeFile.tmp"
				reversePatchError=true
			fi
		else
			logMessage "CRITICAL: $packageName $baseName - no prevoius patch file"
			reversePatchError=true
		fi
		# if the reverse patch failed, remove mods from ALL packages
		#### TODO: DRASTIC but can't think of a way around this
		if $reversePatchError ; then
			message1="CRITICAL: $package $baseName could not be uninstalled cleanly"
			message2="  $remainingPackages must be uninstalled and reinstalled"
			logMessage "$message1"
			logMessage "$message2"
			echo "$message1" >> "$scriptDir/patchErrors"
			echo "$message2" >> "$scriptDir/patchErrors"

			# report errors to other packages as well
			for otherPackage in $remainingPackages ; do
				otherPkgPatchErrors="$packageRoot/$otherPackage/patchErrors"
				echo "$message1" >> "$otherPkgPatchErrors"
				echo "$otherPackage must be uninstalled and reinstalled" >> "$otherPkgPatchErrors"
				# remove previous patch in other package(s) since it no longer applies
				rm -f "$previousPatchesRoot/$otherPackage/$baseName.patch"
			done
			setInstallFailed $EXIT_PATCH_ERROR "patch error details were saved in $packageName/patchErrors"
		fi
	fi

	# always remove previous patch file
	rm -f "$previousPatchFile"

	# restore original if no other packages have modified this active file
	#	or if reverse patch failed
	if $restoreOriginal || $reversePatchError ; then
		if [ -e "$activeFile.orig" ]; then
			mv -f "$activeFile.orig" "$activeFile"
			thisFileUpdated=true
		elif [ -f "$activeFile.NO_ORIG" ]; then
			rm -f "$activeFile"
			rm -f "$activeFile.NO_ORIG"
			thisFileUpdated=true
		fi

		rm -f "$packageList"

	# there are other packages, remove only this package from list
	else
		grep -v "$packageName" "$packageList" | tee "$packageList" > /dev/null
	fi

	# remove file from installed file list
	if [ -f "$installedFilesList" ]; then
		grep -v "$activeFile" "$installedFilesList" | tee "$installedFilesList" > /dev/null
	fi
	if $thisFileUpdated; then
		updateRestartFlags "$activeFile"
		return 0
	else
		return 1
	fi
} # end restoreActiveFile ()


# checkFileSets validates the file sets used to install package modifications
#
# If a file set for the current Venus OS version exists, the replacement files in that file set
#	are usable as is and no further checks are needed.
# The COMPLETE flag file indicates that the file set was validated on the computer creating
#	the file sets and all replacement files (or symlinks to other file sets) exist.
#	No checks are needed for a COMPLETE file set.
# If not, an attempt is made to create a file set for the current Venus OS version
#   If the new active files for the new version all match another version
#       the new file set is populated automatically with replacement files from the other version
#		and may be used with no further action
#   If not, new file set is marked INCOMPLETE and installation failure information is set
#	The package can not be installed on this Venus OS version
#
# Replacement files that have no original specify an "alternate original" that is used
# for version comparisons that locate an appropriate replacement

checkFileSets ()
{
	# no file sets - nothing to check
	if ! [ -e "$pkgFileSets" ]; then return; fi

	# no checks needed if all replacement files exist in the selected file set
	if [ -f "$fileSet/COMPLETE" ]; then return; fi

	# sort versionList in reverse version order to make searches faster
	# since newer versions will most likely contain the desired files to create a new file set
	local rawVersionList=($(ls -d "$pkgFileSets"/v* 2> /dev/null))
    local tempList=()
    local fs
    local baseName
    local version
    local versionNumber
	for fs in ${rawVersionList[@]} ; do
		version=$(basename $fs)
		versionStringToNumber $version
		tempList+=("$version:$versionNumber")
	done
	local versionList=( $(echo ${tempList[@]} | tr ' ' '\n' | sort -t ':' -r -n -k 2 | uniq ) )
	
	# versioned file sets exist but empty file list
	if [ ! -z "$versionList" ] && [ -z "$fileList" ]; then
        setInstallFailed $EXIT_FILE_SET_ERROR "empty file list"
		touch "$fileSet/INCOMPLETE"
		rm -f "$fileSet/COMPLETE"
        return
	# no versioned file sets - nothing to validate - allow install
	elif [ -z "$versionList" ]; then return; fi

	# attempt to create a new file set or validate an existing one not marked as COMPLETE

	rm -f "$fileSet/INCOMPLETE"

    # attempt to create file set if it doesn't exist
    if [ ! -d "$fileSet" ]; then
        logMessage "creating a new file set for $venusVersion"
        mkdir "$fileSet"
    fi

    for file in ${fileList[@]} ; do
        baseName=$(basename "$file")

		# version-independent file exists
		if [ -e "$versionIndependentFileSet/$baseName" ] || [ -e "$pkgFileSets/$baseName" ]; then
			# no versioned files (only version-independent found) - skip version checks
			if [ -z $( find "$fileSet"/v* -name $baseName ) ]; then
				continue
			# continue with tests if any versioned files exist
			else
				logMessage "WARNING $baseName versioned file exists - version-independent file will be ignored"
			fi
		fi

        # skip checks if replacement file already exists
        # or if there is no replacement file needed
        if [ -f "$fileSet/$baseName" ] || [ -f "$fileSet/$baseName.USE_ORIGINAL" ]; then
            rm -f "$fileSet/$baseName.NO_REPLACEMENT"
			continue
        fi

        local activeFile
        if [ -f "$altOrigFileDir/$baseName.ALT_ORIG" ]; then
            activeFile=$(cat "$altOrigFileDir/$baseName.ALT_ORIG")
        elif [ -f "$pkgFileSets/$baseName.ALT_ORIG" ]; then
            activeFile=$(cat "$pkgFileSets/$baseName.ALT_ORIG")
		else
			activeFile=$file
        fi
		if ! [ -e $( dirname "$activeFile" ) ]; then
			logMessage "no parent directory $activeFile - skipping checks"
			continue
		fi

        # package already installed, use .orig file for comparisons
        if [ -f "$activeFile.orig" ]; then
            activeFile="$activeFile.orig"
        fi

        # can't process if no original (aka active) file exists in the file set
        if [ ! -f "$activeFile" ]; then
            logMessage "ERROR $venusVersion $baseName no active file"
            touch "$fileSet/$baseName.NO_ACTIVE_FILE"
			touch "$fileSet/INCOMPLETE"
            continue
        fi

        # if an active file exists look for a match in another file set
        if [ ! -z "$activeFile" ]; then
            matchFound=false
            for entry in ${versionList[@]}; do
				otherVersion=$(echo $entry | awk -F ':' '{print $1}')

                # skip this version
                if [ "$venusVersion" = "$otherVersion" ]; then
                    continue
                fi

                otherFile="$pkgFileSets/$otherVersion/$baseName"

                # skip symbolic links and nonexistent originals
                if [ ! -f "$otherFile.orig" ] || [ -L "$otherFile.orig" ] ; then
                    continue
                fi
                
                # files match
                if cmp -s "$activeFile" "$otherFile.orig" > /dev/null ; then
                    matchFound=true
                    break
                fi
            done
 
            if $matchFound ;then
                rm -f "$fileSet/$baseName.orig"
                rm -f "$fileSet/$baseName.NO_ORIG"
                # if other file set contains a replacement file, link to it
                if [ -f "$otherFile" ]; then
					rm -f "$fileSet/$baseName"
                    ln -s "../$otherVersion/$baseName" "$fileSet/$baseName"
                    rm -f "$fileSet/$baseName.NO_REPLACEMENT"
                    rm -f "$fileSet/$baseName.USE_ORIGINAL"
                # if other file set does not contain a replacement, this one will not either
                # this IS permitted and handled in the updateActiveFile and restoreActiveFile functions
                elif [ -f "$otherFile.USE_ORIGINAL" ]; then
                    touch "$fileSet/$baseName.USE_ORIGINAL"
                    rm -f "$fileSet/$baseName.NO_REPLACEMENT"
                fi
            # no match to a previous verison - can't create file set automatically
            # but copy original file to aid manual editing
            else
                logMessage "ERROR $venusVersion $baseName no replacement file"
                cp "$activeFile" "$fileSet/$baseName.orig"
                touch "$fileSet/$baseName.NO_REPLACEMENT"
                touch "$fileSet/INCOMPLETE"
            fi
        fi
    done

    if [ -f "$fileSet/INCOMPLETE" ]; then
        setInstallFailed $EXIT_FILE_SET_ERROR "incomplete file set for $venusVersion"
	# if we get this far and fs is not marked INCOMPLETE, then the file set does not need to be checked again next pass
	else
		touch "$fileSet/COMPLETE"
    fi
}

# builds the installedFilesList and installedServices lists from the package's setup script
# this is needed for installs with a prior version of SetupHelper that did not
#	create the installed... lists
#
# uninstall... functions then use these lists to uninstall

buildUninstallListsfromSetupScript ()
{
	# prevent this from running a second time
	if [ "$uninstallListsAlreadyBuilt" == 'yes' ]; then
		return
	fi
	uninstallListsAlreadyBuilt='yes'
	local param

	scriptUninstallFilesList=()
	scriptUninstallServicesList=()
	while read -r line || [[ -n "$line" ]]; do
		commandFound=false
		read -a params <<< $line
		numberOfParams=${#params}
		for (( i=0; i < $numberOfParams; i++ ));do
			case "${params[i]}" in
			updateActiveFile)
				# parameter of intrest is the second one if it exists
				# otherwise, it should be the first one
				param=${params[i+2]}
				if [ -z "$param" ] || [[ $param == \#* ]]; then
					param=$( echo ${params[i+1]} | sed s?'$qmlDir'?$qmlDir? )
				fi
				if ! [ -z "$param" ] && ! [[ $param == \#* ]]; then
					# remove any  quotes around parameters
					param=$( echo $param | sed -e 's?"?? g' -e "s?'?? g" )
					scriptUninstallFilesList+=("$param")
				fi
				;;
			installService)
				# parameter of intrest is the first one if it exists
				# otherwise, is the packageName
				param=${params[i+1]}
				if ! [ -z "$param" ] && ! [[ $param == \#* ]]; then
					scriptUninstallServicesList+=("$param")
				else
					# remove any quotes around parameters
					param=$( echo $param | sed -e 's?"?? g' -e "s?'?? g" )
					scriptUninstallServicesList+=($packageName)
				fi
				;;
			esac
		done
	done < "$scriptDir/setup"
}

# install / uninstall all files / services functions
#
# the install... functions are called from endScript if the related flags are set
#	but may also be called in the setup script
#	if processing needs to be done after the files are installed
#
# NOTE: uninstall functions are called in endScrpit during an uninstall
#			there are no flags to control this !
#
# NOTE: only services in the package's serice directory are installed
#		any services in the package's root diretory are not installed


installAllFiles ()
{
	if [ -z "$fileListAll" ]; then
		getFileLists "$pkgFileSets"
	fi

	if [ ! -z "$fileListAll" ]; then
		logMessage "installing files"
		local file
		for file in ${fileListAll[@]}; do
			if $installFailed ; then break; fi
			updateActiveFile "$file"
		done
	fi
}

# uninstall files from
#	installed files list if present
#	and from file lists in the package
#	and from updateActiveFile calls found in the setup script
# this insures complete uninstall even for packages installed
#	with SetupHelper prior to v6.0

uninstallAllFiles ()
{
	local restoreFilesList=()
	# add installdeFilesList if present (might be empty but that's fine)
	if [ -f "$installedFilesList" ]; then
		restoreFilesList=( $( cat "$installedFilesList" ) )
	fi
	# add file lists & calls to installActiveFile in setup script
	restoreFilesList+=( ${fileListAll[@]} )
	buildUninstallListsfromSetupScript
	restoreFilesList+=( ${scriptUninstallFilesList[@]} )

	# remove duplicates
	if (( ${#restoreFilesList[@]} > 1 )); then
		restoreFilesList=( $(printf "%s\n" "${restoreFilesList[@]}" | sort -u ) )
	fi

	# uninstall the files
	if [ ! -z "$restoreFilesList" ]; then
		logMessage "uninstalling files"
		local file
		for file in ${restoreFilesList[@]}; do
			restoreActiveFile $file
		done
	fi
}


installAllServices ()
{
	local serviceList
	local service
	# get list of services in the package's service directory
	if [ -d "$servicesDir" ]; then
		servicesList=( $( cd "$servicesDir"; ls -d * 2> /dev/null ) )
		if ! [ -z "$servicesList" ]; then
			logMessage "installing services"
			for service in ${servicesList[@]} ; do
				if $installFailed; then break; fi
				installService $service
			done
		fi
	fi
}


# uninstal services found in the services directory
#	and restoreServices call in the package's setup script

uninstallAllServices ()
{
	local tempList=()
	local servicesList=()
	local service

	if [ -f "$installedServicesList" ]; then
		servicesList=( $( cat "$installedServicesList" ) )
	fi
	# add list from the setup script itself
	buildUninstallListsfromSetupScript
	servicesList+=( ${scriptUninstallServicesList[@]} )

	# remove duplicates
	if (( ${#servicesList[@]} > 1 )); then
		servicesList=( $(printf "%s\n" "${servicesList[@]}" | sort -u ) )
	fi

	# uninstall services
	if [ ! -z "$servicesList" ]; then
		logMessage "uninstalling services"
		for service in ${servicesList[@]} ; do
			if [ -z "$service" ]; then
				removeService $packageName
			else
				removeService $service
			fi
		done
	fi
}


# restart the GUI V1 service
# begining at about v3.20~18, changes were made to accommodate the gui-v2
# and these changes require different handling of a GUI service restart


restartGuiV1Service ()
{
	# gui is the older service that runs GUI v1 only
	if [ -e "/service/gui" ]; then
		logMessage "restarting GUI V1 (/service/gui)"
		svc -t "/service/gui"
	# restart GUI if NOT running v2 or can't determine if GUI v1 or v2 is selected
	elif [ -e "/service/start-gui" ]; then
		local guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')"
		if (( $guiVersion != 2 )); then
			logMessage "restarting GUI V1 (/service/start-gui)"
			svc -t "/service/start-gui"
		fi
	fi
}

# for backward compatibility (oler setup scripts)
restartGuiService ()
{
	restartGuiV1Service
}

restartGuiV2Service ()
{
	# restart GUI if NOT running v1 or can't determine if GUI v1 or v2 is selected
	if [ -e "/service/start-gui" ]; then
		local guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')"
		if (( $guiVersion != 1 )); then
			logMessage "restarting GUI V2 (/service/start-gui)"
			svc -t "/service/start-gui"
		fi
	fi
}

# determine how the setup script should exit based on $scriptAction and other flags
# services may be restarted here also
#
# endScript accepts these optional parameters which determines if files and/or services are installed
#
#	'INSTALL_FILES' causes files from the file lists to be installed
#	'INSTALL_SERVICES' causes services in the services directory to be installed
#		all services must be in the package's services directory
#	'ADD_DBUS_SETTINGS' will add/update dBus settings from the DbusSettingsList
#	any or may be included
# do NOT include these if related processing is needed in the setup script !
# instead, call installAllFiles and/or installAllServices in line with the other processing
#	or call updateActiveFile or installService directly
#
# this function completes package installation 
#	and sets up conditions for reinstallation following a Venus Os firmware update
#
# may EXIT or REBOOT within the function - DOES NOT RETURN TO CALLER           


endScript ()
{
    if [ $scriptAction == 'INSTALL' ] && ! $installFailed ; then
		# do installs as indicated from caller
		while (( $# > 0 )); do
			case "$1" in
				'INSTALL_FILES')
					installAllFiles
					;;
				'INSTALL_SERVICES')
					installAllServices
					;;
				'ADD_DBUS_SETTINGS')
					addAllDbusSettings
					;;
			esac
			shift
		done

		# assume that if we get this far, any command line opitons have already been set
		touch "$setupOptionsDir/optionsSet"
		
		# clear flag preventing auto installs in PackageManager
		rm -f "$setupOptionsDir/DO_NOT_AUTO_INSTALL"

		# if script needs to run again, installedVersionFile flag file is removed
		# script should run again at boot time via reinstallMods
		if $runAgain ; then
		  logMessage "script will run again at startup"
			rm -f "$installedVersionFile"
		# otherwise, installation is complete - update installedVersion
		else
			cp "$scriptDir/version" "$installedVersionFile"
		fi

		# update rc.local to include call to reinstallMods
		#	do only for SetupHelper since other packages are now installed by PackageManager
		if [ "$packageName" == "SetupHelper" ]; then
			if [ ! -f "$rcLocal" ]; then
				logMessage "creating $rcLocal"
				cp "$scriptDir/rcS.local" "$rcLocal"
				chmod +x "$rcLocal"
			elif  [ $(grep -c "blind install" "$rcLocal") -gt 0 ]; then
				logMessage "REPLACING blind install $rcLocal with the standard one"
				rm -f "$rcLocal"
				cp "$scriptDir/rcS.local" "$rcLocal"
				chmod +x "$rcLocal"
			elif  [ $(grep -c "SetupHelper" "$rcLocal") == 0 ]; then
				logMessage "adding SetupHelper reinstall script to $rcLocal"
				sed -e '1,2d' "$scriptDir/rcS.local" >> $rcLocal
			fi
		fi
	fi
		

    if [ $scriptAction == 'UNINSTALL' ] ; then
		if $installFailed ; then
			logMessage "INSTALL failed - attempting UNINSTALL in endScript"
		# package was actually uninstalled (not an install failure) - set flag preventing auto installs
		else 
			touch "$setupOptionsDir/DO_NOT_AUTO_INSTALL"
		fi

		# ALWAYS attempt to uninstall files and services
		uninstallAllFiles
		uninstallAllServices

        # flag package not installed since package is being removed
        rm -f "$installedVersionFile"

		# when uninstalling SetupHelper, remove EMPTY installed...Lists in all packages
		#	to prevent a future install with an older SetupHelper
		#	confusing a future future uninstall with a new SetupHelper
		if [ "$packageName" == "SetupHelper" ] && [ -e "$installedFilesDir" ]; then
			for file in $(ls "$installedFilesDir") ; do
				if ! [ -s "$installedFilesDir/$file" ]; then
					rm "$installedFilesDir/$file"
				fi
			done
			# remove lines from rcS.local
			sed -i -e "/# SetupHelper reinstall/,/fi/d" "$rcLocal"
		fi
	fi

	# setup script signals nothing to do - exit without further action without errors
	if ! $installFailed && ! $uninstallFailed; then
		if [ $scriptAction == 'EXIT' ]; then
			exit $EXIT_SUCCESS
		elif ! [ $scriptAction == 'INSTALL' ] && ! [ $scriptAction == 'UNINSTALL' ]; then
			setInstallFailed $EXIT_ERROR "unexpected script action $scriptAction - did not install or uninstall"
			exit $EXIT_ERROR
		fi
	fi

	# check for reboot and service restarts
	if $rebootNeeded ; then
		if $userInteraction ; then
			if yesNoPrompt "Reboot system now (y) or do it manually later (n): " ; then
				deferReboot=false
			else
				logMessage "system must be rebooted to finish installation and activate components"
				deferReboot=true
			fi
		fi
	fi

	# restart services if a reboot won't happen below
	if ! $rebootNeeded || $deferReboot ; then
		if $restartGeneratorService ; then
			logMessage "restarting generator service"
			if [ -e "svc -t /service/dbus-generator" ]; then
				svc -t /service/dbus-generator
			fi
			if [ -e "svc -t /service/dbus-generator-starter" ]; then
				svc -t /service/dbus-generator-starter
			fi
		fi
		if $restartSystemCalc ; then
			logMessage "restarting systemcalc service"
			svc -t /service/dbus-systemcalc-py
		fi
		if $restartDigitalinputs ; then
			logMessage "restarting digital inputs service"
			svc -t /service/dbus-digitalinputs
		fi
	fi
	#### TODO: add gui v2
	# restart GUI if not doing a reboot below
	if $restartGui && ! $rebootNeeded && ! $deferReboot; then
		if $userInteraction ; then
			if yesNoPrompt "Restart the GUI now (y) or issue a do it manually later (n): " ; then
				restartGuiV1Service
				deferGuiRestart=false
				restartGui=false
			fi
		else
			# GUI restart NOT deferred - do it now
			if ! $deferGuiRestart ; then
				restartGuiV1Service
				deferGuiRestart=false
				restartGui=false
			fi
		fi
	fi

	local exitCode=$EXIT_SUCCESS
	if $installFailed ; then
		if [ $scriptAction == 'UNINSTALL' ]; then
			if $uninstallFailed ; then
				logMessage "CRITICAL: install failed with error $installExitReason"
				logMessage "CRITICAL: uninstall also failed with error $uninstallExitReason - package state unknown"
				exitCode=$uninstallExitReason
			else
				logMessage "install failed - package has been successfully uninstalled"
				exitCode=$installExitReason
			fi
		else
			logMessage "install failed during prechecks - no changes were made"
			exitCode=$installExitReason
		fi
	elif $uninstallFailed ; then
		logMessage "CRITICAL: uninstall failed - package state unknown"
		exitCode=$installExitReason
	elif $rebootNeeded ; then
		if $deferReboot ; then
			logMessage "reboot needed to complete operaiton"
			exitCode=$EXIT_REBOOT
		else
			logMessage "rebooting ..."
			reboot
		fi
	elif $restartGui && $deferGuiRestart ; then
		echo "GUI must be restarted to activate changes"
		exitCode=$EXIT_RESTART_GUI
	# install/uninstall succeeded
	else
		logMessage "complete - no errors"
		exitCode=$EXIT_SUCCESS
	fi

	exit $exitCode
} # endScript ()

######## this code is executed in-line when CommonResources is sourced

# check for reinstall parameter
# set $scriptAction to control work following the source command
# if "force" is also provided on the command line, then the installedVersionFile is not checked
# installedVersionFile contains the installed version (if any)
# it is compared to the version file in the package directory
#  if installedVersionFile is missing or contents are different, the installation will proceed
# if the two versions match, there is no need to reinstall the package
# we assume a reinstall is always run without benefit of a console (runningAtBoot will be true)
# so there will be no prompts and all actions will be automatic
#
# "deferReboot" signals that endScript should not reboot the system, but return EXIT_REBOOT
#	assuming the caller will evenutally reboot the system
#
# "deferGuiRestart" is similar for restarting the GUI
#
# "install" causes the package to be installed silently
# "uninstall" causes the package to be uninstalled silently
#
# command line parameters may appear in any order
#
#
# logToConsole is set to true in the LogHandler script
# It is set to false here the 'auto' parameter is passed on the command line
#	which indicates this script is NOT being run from the command line

# cleanup from previous versions - reinstallScriptsList no loner used
rm -f "/data/reinstallScriptsList"

# initialize version strings and numbers for future checks
if [ -f "$installedVersionFile" ]; then
	installedVersion=$(cat "$installedVersionFile")
	versionStringToNumber $installedVersion
	installedVersionNumber=$versionNumber
else
	installedVersion=""
	installedVersionNumber=0
fi

packageVersionFile="$scriptDir/version"
if [ -f "$packageVersionFile" ]; then
	packageVersion=$(cat "$packageVersionFile")

	versionStringToNumber $packageVersion
	packageVersionNumber=$versionNumber
else
	packageVersion=""
	packageVersionNumber=0
fi

# collect command line options
reinstall=false
deferReboot=false
deferGuiRestart=false
userInteraction=true
runFromPm=false
while [ $# -gt 0 ]; do
	case $1 in
		"reinstall")
			reinstall=true
			;;
		"deferReboot")
			deferReboot=true
			;;
		"deferGuiRestart")
			deferGuiRestart=true
			;;
		"install")
			scriptAction='INSTALL'
			;;
		"uninstall")
			scriptAction='UNINSTALL'
			;;
		"auto")
			logToConsole=false
			userInteraction=false
			;;
		"runFromPm")
			runFromPm=true
			logToConsole=false 
			userInteraction=false
			deferReboot=true
			deferGuiRestart=true
			;;
		"check")
			# if no other actions were set, set it here
			#	but allow other actions to override this one
			if [ $scriptAction == 'NONE' ]; then
				scriptAction='CHECK'
			fi
			;;
		*)
	esac
    shift
done

# do after logToConsole is enabled/disabled abvove
logMessage "--- starting setup script $packageVersion action: $scriptAction"
if ! $runFromPm && $logToConsole ; then
	echo
	echo "messages are NOT written to log file - only to console"
	echo
fi

# packages that require options to proceed unattended
# must include the optionsRequried flag file in their package directory
# if the flag is present and options haven't been previously set,
#	SD/USB media will be checked for the package options directory
#	and copy them into position

opitonsRequiredFile="$scriptDir/optionsRequired"
optionsSet=false
if [ -f $opitonsRequiredFile ]; then
	if [ -f "$setupOptionsDir/optionsSet" ]; then
		optionsSet=true
	# options not set - check media for options if doing a blind install
	elif [ $scriptAction == 'INSTALL' ]; then
		mediaList=($(ls /media))
		for dir in ${mediaList[@]} ; do
			altSetupDir="/media/$dir/"$(basename $setupOptionsRoot)"/$packageName"
			if [ -f "$altSetupDir/optionsSet" ]; then
				cp -r "$altSetupDir" "$setupOptionsRoot"
				if [ -f "$setupOptionsDir/optionsSet" ]; then
					logMessage "options retrieved from SD/USB media"
					optionsSet=true
				fi
				break
			fi
		done
	fi

# no command line options are needed - ok to reinstall even if
#	setup was not run from the command line
else
	optionsSet=true
fi

# called from reinstallMods at boot time
if $reinstall ; then
    runningAtBoot=true
	# not installed, do it now
    if (( installedVersionNumber == 0 )); then
        scriptAction='INSTALL'
	# check versions and install only if package version is newer than installed version
	else
		# trigger install if version numbers differ
		if (( installedVersionNumber != packageVersionNumber )); then
			scriptAction='INSTALL'
		else
			exit $EXIT_SUCCESS
		fi
	fi

# not running from reinstallMods
else
    runningAtBoot=false
fi

if [ ! -d "$setupOptionsDir" ]; then
	logMessage "creating package options directory $setupOptionsDir"
	mkdir -p $setupOptionsDir
fi

# initialze integer version number for venus version
# used below and in checkFileSets
versionStringToNumber $venusVersion
venusVersionNumber=$versionNumber

getFileLists "$pkgFileSets"

# create temporary directory for temporary install/uninstall files (unique temp directory in volatile storage)
#	updateActiveFile always checks this location first for a replacement before checking file sets
# tempFileDir is removed in the exit trap above but it is in volatile storage so will be removed on boot anyway
tempFileDir=$( mktemp -d )

# patch files previously used to patch a file are stored in /etc/venus
# so they track the selected root fs and are erased when Venus OS is updated
previousPatchesRoot="/etc/venus/previousPatches"
previousPatchesDir="$previousPatchesRoot/$packageName"

# do install pre-checks - skip if uninstalling
if [ $scriptAction != 'UNINSTALL' ]; then

	# prevent installing Raspberry Pi packages on other platforms
	if [ -f "$scriptDir/raspberryPiOnly" ]; then 
		if [[ $machine != *"raspberrypi"* ]]; then
			setInstallFailed $EXIT_INCOMPATIBLE_PLATFORM "$packageName not compatible with $machine"
		fi
	fi
	# check to see if package is compatible with this Venus version
	if [ -f "$scriptDir/firstCompatibleVersion" ]; then
		firstCompatibleVersion=$(cat "$scriptDir/firstCompatibleVersion")
	# no first compatible version specified - use the default
	else
		firstCompatibleVersion='v3.10'
	fi
	if [ -f "$scriptDir/obsoleteVersion" ]; then
		obsoleteVersion=$(cat "$scriptDir/obsoleteVersion")
		versionStringToNumber $obsoleteVersion
		obsoleteVersionNumber=$versionNumber
	else
		obsoleteVersionNumber=9999999999999999
	fi

	versionStringToNumber $firstCompatibleVersion
	firstCompatibleVersionNumber=$versionNumber
	if (( $venusVersionNumber < $firstCompatibleVersionNumber )); then
		setInstallFailed $EXIT_INCOMPATIBLE_VERSION "$venusVersion before first compatible $firstCompatibleVersion"
	elif (( $venusVersionNumber >= $obsoleteVersionNumber )); then
			setInstallFailed $EXIT_INCOMPATIBLE_VERSION "$venusVersion after last compatible $obsoleteVersion"
	# validate firmware version if valid firmware version file exists
	elif [ -e "$scriptDir/validFirmwareVersions" ]; then
		incompatibleVersion=false
		if ! grep -xq "$venusVersion" "$scriptDir/validFirmwareVersions" ; then
			incompatibleVersion=true
		fi
		# user can override this from command line (for testing new versions mainly)
		if $incompatibleVersion && $userInteraction ; then
			if yesNoPrompt "$venusVersion not in the valid firmware list proceed any way (y/n)? " ; then 
				incompatibleVersion=false
			fi
		fi
		if $incompatibleVersion ; then
			setInstallFailed $EXIT_INCOMPATIBLE_VERSION "$venusVersion not in valid firmware list"
		fi
	fi

	# determine if GUI v1 is installed
	#	Note, it may NOT be running or selected to run!!!!
	if [ ! -d "/opt/victronenergy/gui" ]; then
		guiV1present=false
	else
		guiV1present=true
	fi
	# block installs if any GUI files would be modified and GUI v1 is not present
	# packages can bypass GUI v1 checks and allow installs even if package contains them
	if [ -f "$scriptDir/GUI_V1_NOT_REQUIRED" ]; then
		guiV1required=false
	# files in the GUI v1 directory are considered mandatory
	elif (( $( cat "$pkgFileSets/fileList"* 2>/dev/null | grep -c '/gui/' ) > 0 )); then
		guiV1required=true
	# look also in setup script
	elif (( $( grep 'updateActiveFile' "$scriptDir/setup" | grep -c '$qmlDir\|/gui/') > 0 )); then
		guiV1required=true
	else
		guiV1required=false
	fi
	if ! $guiV1present && $guiV1required ; then
		setInstallFailed $EXIT_NO_GUI_V1 "$packageName requires GUI v1"
	fi

	# attempting an install without the comand line prompting
	#	and needed options have not been set yet - can't continue
	if ! $installFailed && [ $scriptAction == 'INSTALL' ]; then
		if ! $optionsSet ; then
			setInstallFailed $EXIT_OPTIONS_NOT_SET "required options have not been set"
		fi
	fi

	checkFileSets

	# checkFileSets created a missing file set and set the INCOMPLETE flag if needed
	# that is all CHECK needed to do so EXIT HERE !!!!
	if [ $scriptAction == 'CHECK' ]; then
		if [ -f "$fileSet/INCOMPLETE" ]; then
			exit $EXIT_FILE_SET_ERROR
		fi
	fi

	checkPackageDependencies

	# create patched files for files with VisibleItemModel for older Venus OS versions
	if ! $installFailed ; then
		versionStringToNumber "v3.00~14"
		if (( $venusVersionNumber < $versionNumber )); then
			logMessage "patching VisibleItemModel to VisualItemModel in all .qml replacements"
			for file in ${fileListVersionIndependent[@]};  do
				baseName=$( basename "$file" )
				if ! [[ "$baseName" == *.qml ]]; then continue; fi
				sourceFile="$versionIndependentFileSet/$baseName"
				if ! [ -f "$sourceFile" ]; then continue; fi
				if (( $(grep -c "VisibleItemModel" "$sourceFile") == 0 )); then continue; fi
				sed -e 's/VisibleItemModel/VisualItemModel/' "$sourceFile" > "$tempFileDir/$baseName"
			done
		fi

		# create patched files for all qml files for the change to QtQuick 2
		versionStringToNumber "v3.60~18"
		if (( $venusVersionNumber >= $versionNumber )); then
			logMessage "changing QtQuick 1.1 to QtQuick 2 in all .qml replacements"
			for file in ${fileListVersionIndependent[@]};  do
				baseName=$( basename "$file" )
				if ! [[ "$baseName" == *.qml ]]; then continue; fi
				sourceFile="$versionIndependentFileSet/$baseName"
				if ! [ -f "$sourceFile" ]; then continue; fi
				if (( $(grep -c "QtQuick 1.1" "$sourceFile") == 0 )); then continue; fi
				sed -e 's/QtQuick 1.1/QtQuick 2/' "$sourceFile" > "$tempFileDir/$baseName"
			done
		fi
	fi

	# create the forward and reverse patched files
	# used during the actual install and to test if the patch/reverse patch will succeed
	# done here so PackageManager knows if this will be possible before starting the install
	#
	# if this and other packages have both modified the active file,
	#	the patch from this package is first removed
	#		by reverse patching the active file with the PREVIOUS patch file
	#	the new patch is then applied
	#	a test reverse patch insures the patch can be removed in the future
	#	the patch file used for this patch is then saved so it can be used
	#		for the reverse patch on next install/uninstall
	#
	# if no other packages have modified the active file,
	#	the patch is applied to .orig file if exists
	#	rather than reverse patching the active file
	#	this maintains compatibility with packages installed with older versions of SetupHelper

	# use local patch executable - BusyBox version has bugs and does not support all options
	# include options that are used in all calls
	# RPI 5 builds use a different binary format so choose the version that works
	if $( /data/SetupHelper/patch -v &> /dev/null ); then
		patch="/data/SetupHelper/patch --force --silent --reject-file=/dev/null"
	elif $( /data/SetupHelper/patchBookworm -v &> /dev/null ); then
		patch="/data/SetupHelper/patchBookworm --force --silent --reject-file=/dev/null"
	else
		patch=""
		echo "patch executable incompatible with this OS version" > "$scriptDir/patchErrors"
		setInstallFailed $EXIT_PATCH_ERROR "patch executable incompatible with this OS version - can't continue"
		# pre-checks only - direct exit
		if [ $scriptAction == 'CHECK' ]; then
			exit $EXIT_PATCH_ERROR
		# script will exit since we are still in pre-checks !!
		else
			endScript
		fi
	fi

	if ! $installFailed && ! [ -z "$fileListPatched" ]; then
		patchErrors=()
		for activeFile in ${fileListPatched[@]}; do
			baseName=$( basename $activeFile )
			tempActiveFile="$tempFileDir/$baseName.tmp"
			forwardPatched="$tempFileDir/$baseName.patchedForInstall"
			currentPatchFile="$tempFileDir/$baseName.currentPatch"
			previousPatchFile="$previousPatchesDir/$baseName.patch"

			if ! [ -e "$activeFile" ] ; then
				logMessage "no active file $activeFile - skipping patch"
				continue
			fi

			rm -f "$currentPatchFile"

			# check for this and other packages in .package list
			packageList="$activeFile.package"
			thisPackageInList=false
			otherPackagesInList=false
			if [ -f "$packageList" ]; then
				previousPackages=$( cat "$packageList" )
				for previousPackage in ${previousPackages[@]}; do
					if [ $packageName == $previousPackage ]; then
						thisPackageInList=true
					else
						otherPackagesInList=true
					fi
				done
			fi

			patchOk=false
			skipPatch=false

			if $thisPackageInList; then
				if ! $otherPackagesInList ; then
					# only this package modified active file
					# ignore any previous patch and patch .orig file
					if [ -e "$activeFile.orig" ]; then
						cp "$activeFile.orig" "$tempActiveFile"
						patchOk=true
					# use active file if no .orig exists
					else
						cp "$activeFile" "$tempActiveFile"
						patchOk=true
					fi
				# this and others have modified the active file
				# attempt to remove the previous patch for this package
				# then patch the result
				elif [ -e "$previousPatchFile" ]; then
					if $patch --reverse -o "$tempActiveFile" "$activeFile" "$previousPatchFile" &> /dev/null ; then
						patchOk=true
					# reverse patch failed
					else
						patchErrors+=( "$baseName unable to remove previous patch" )
					fi
				else
					patchErrors+=( "$baseName no previous patch file" )
				fi
			# this package has not previously modified the active file
			# patch the active file
			else
				cp "$activeFile" "$tempActiveFile"
				patchOk=true
			fi
			patchSuccess=false
			# a suitable source for the patch was located above
			if $patchOk; then
				# attempt to patch the active file with any file ending in .patch
				# the first one that successfully creates a forward AND reverse patch is used
				# .patchedForInstall provides the patched file for updateActiveFile
				patchFiles=( $( ls "$patchSourceDir/$baseName"*.patch ) )
				for patchFile in ${patchFiles[@]};do
					if $patch --forward -o "$forwardPatched" "$tempActiveFile" "$patchFile" &> /dev/null ; then
						# forward patch succeeded - test reverse patch (both must succeed)
						if $patch --reverse -o /dev/null "$forwardPatched" "$patchFile" &> /dev/null ; then
							patchSuccess=true
							break
						fi
					fi
				done
				if $patchSuccess ; then
					# save this so forwardPatched (created above) is used for install
					# when file is installed, currentPatchFile will be copied to previousPatchFile
					#	for a future uninstall or reinstall
					cp "$patchFile" "$currentPatchFile"
				else
					patchErrors+=( "$baseName patch unsuccesful" )
					rm -f "$forwardPatched"
				fi
			elif ! $skipPatch ; then
				patchErrors+=( "$baseName no patch source" )
			fi
			rm -f "$tempActiveFile"
		done # for activeFile

		# save errors in patchErrors file
		if ! [ -z "$patchErrors" ] ; then
			rm -f "$scriptDir/patchErrors"
			for patchError in "${patchErrors[@]}"; do
				logMessage "$patchError"
				echo "$patchError" >> "$scriptDir/patchErrors"
			done
			setInstallFailed $EXIT_PATCH_ERROR "patch error details were saved in $packageName/patchErrors"

			# pre-checks only - direct exit
			if [ $scriptAction == 'CHECK' ]; then
				exit $EXIT_PATCH_ERROR
			# script will exit since we are still in pre-checks !!
			else
				endScript
			fi
		# no errors - remove the error history
		else
			rm -f "$scriptDir/patchErrors"
		fi
	fi # if fileListPatched
fi # if [ $scriptAction != 'UNINSTALL' ]

# go no further if just checking
if [ $scriptAction == 'CHECK' ]; then
	exit $EXIT_SUCCESS
fi

# in the Victron images, the root FS is read-only and is a minimum size
# in order to install modificaitons, the root partition needs to be
#	remounted read-write and resized to allow mods to be added
# updateRootToReadWrite calls remount-rw.sh or resize2fs.sh
#	then check to make sure there is sufficient space before allowing installs

updateRootToReadWrite

# patch files previously used to patch a file are stored in /etc/venus
# so they track the selected root fs and are erased when Venus OS is updated
if ! [ -e "$previousPatchesDir" ]; then
	mkdir -p "$previousPatchesDir"
fi
# relocate previous patch files
oldPreviousPatchesDir="$setupOptionsDir/previousPatches"
if [ -e "$oldPreviousPatchesDir" ]; then
	logMessage "relocating previous patches"
	mv "$oldPreviousPatchesDir"/* "$previousPatchesDir"
	rm -rf "$oldPreviousPatchesDir"
fi
unset oldPreviousPatchesDir

# done with pre checks
#	prior to this no system mofications have been made
#	after this system modifications may occur

if $installFailed ; then
	if ! $userInteraction ; then
		logMessage "ERROR: errors occured during pre-checks - can't continue"
		# EXIT HERE if errors occured during pre-checks while running unattended !!!!
		# the LAST install failure reported to setInstallFailed is used as the exit code
		exit $installExitReason	# EXIT HERE !!!!
	# command line install request failed - reset scriptAction to NONE
	# so prompts will be shown - eg
	#	to show the log or
	#	to trigger a manual uninstall
	elif  [ $scriptAction == 'INSTALL' ]; then
		logMessage "ERROR: install failed during pre-checks - select another action"
		scriptAction='NONE'
	fi
fi

# create installed files (and services) directory
if [ ! -d "$installedFilesDir" ]; then
	mkdir "$installedFilesDir"
fi

installPreChecks=false


#### do standard prompting, automatic install/uninstall then exit
if [ "$standardPromptAndActions" == 'yes' ]; then
	# prompt only if action hasn't been set yet (from command line)
	if [ "$scriptAction" == 'NONE' ]; then
		standardActionPrompt
	fi
	endScript 'INSTALL_FILES' 'INSTALL_SERVICES' 'ADD_DBUS_SETTINGS'
fi

# otherwise continue with the setup script 

#### continue executing the setup script which sourced this file


================================================
FILE: HelperResources/DbusSettingsResources
================================================
#!/bin/bash

# DbusSettingsResources for SetupHelper
#
# contains a functions and variables necessary to access dbus Settings parameters
# it should be sourced by scripts setting, creating and removing dbus settings
#
# dbus Settings is not operational during system boot when some setup scripts may
# need to make settings changes
# These functions check to see if the settings system is operational and defer
# the set/create/remove activity so the calling script may continue

# dbus Settings funcitons
# These functions encapsulate an interface to dbus Settings
# NOTE: dbus Settings resources are not always active when it is necessary for
# scripts to make changes or create/remove settings
# it is up to the caller to insure dbus Settings resources are active before callling
# these functions
# a dbus exeption error will be logged if settings are not active yet


# updateDbusStringSetting
# updateDbusIntSetting
# updateDbusRealSetting
#   updates a dbus setting parameter with a new value
#
# if the setting does not exist, it is created
# but max and min values are not set and the default is "", 0 or 0.0 depending on data type
# if these are needed use the dbus command directly
#   this can also be faster if lots of settings must be created at the same time
#
# other data types may exist and would need their own function
#
# $1 is the path to the setting starting with /Settings
# $2 is the new value
#
# if the setting does not yet exist, it is created, then updated to the new value

updateDbusStringSetting ()
{
	# don't do any work if install has already failed
	if $installFailed; then
		return
	fi

    dbus-send --system --print-reply=literal --dest=com.victronenergy.settings "$1"\
            com.victronenergy.BusItem.GetValue &> /dev/null
    if (( $? != 0 )); then
        logMessage "creating dbus Setting $1"
        dbus -y com.victronenergy.settings / AddSettings "%[ {\"path\":\"$1\", \"default\":\"\"} ]" &> /dev/null
    fi

	dbus -y com.victronenergy.settings "$1" SetValue -- "$2" &> /dev/null
}


updateDbusIntSetting ()
{
	# don't do any work if install has already failed
	if $installFailed; then
		return
	fi

    dbus-send --system --print-reply=literal --dest=com.victronenergy.settings "$1"\
            com.victronenergy.BusItem.GetValue &> /dev/null
    if (( $? != 0 )); then
        logMessage "creating dbus Setting $1"
        dbus -y com.victronenergy.settings / AddSettings "%[ {\"path\":\"$1\", \"default\":0} ]" &> /dev/null
    fi

    dbus -y com.victronenergy.settings "$1" SetValue -- "$2" &> /dev/null
}


updateDbusRealSetting ()
{
	# don't do any work if install has already failed
	if $installFailed; then
		return
	fi

    dbus-send --system --print-reply=literal --dest=com.victronenergy.settings "$1"\
            com.victronenergy.BusItem.GetValue &> /dev/null
    if (( $? != 0 )); then
        logMessage "creating dbus Setting $1"
        dbus -y com.victronenergy.settings / AddSettings "%[ {\"path\":\"$1\", \"default\":0.0} ]" &> /dev/null
    fi

    dbus -y com.victronenergy.settings "$1" SetValue -- "$2" &> /dev/null
}



# addAllDbusSettings adds settings from DbusSettingsList in the package directory
# the format of each line is:
# {"path":"/Settings/GuiMods/ShortenTankNames", "default":1, "min":0, "max":1}
# min and max are optional

addAllDbusSettings ()
{
	local settings

	if [ -f "$scriptDir/DbusSettingsList" ]; then
		logMessage "updating dbus Settings"
		while read -r line || [[ -n "$line" ]]; do
			settings+="$line, "
		done < "$scriptDir/DbusSettingsList"

		dbus -y com.victronenergy.settings / AddSettings "%[ $settings  ]" &> /dev/null
	fi
}

# same as above but removes them
# typically settings are retained when removing a package so
# the developer must make this call specifically in the setup script's UNINSTALL section
# if they wish to remove the settings

removeAllDbusSettings ()
{
	local settings

	if [ -f "$scriptDir/DbusSettingsList" ]; then
		logMessage "removing dbus Settings"
		while read -r line || [[ -n "$line" ]]; do
				settings+=$( echo $line | awk -F[:,] '{print $2, ","}' )
		done < "$scriptDir/DbusSettingsList"

		dbus -y com.victronenergy.settings / RemoveSettings "%[ $settings  ]"
	fi
}



# removeDbusSettings removes the setting from dbus Settings
#
# all parameters are each a quoted path to the setting to be removed
# e.g., removeDbusSettings "/Settings/foo" "/Settings/bar"
# (including all settings in one dbus call is much faster)

removeDbusSettings ()
{
    logMessage "removing dbus Settings $@"
    local settings=$(echo "$@" | sed -e s_^_\"_ -e s_\$_\"_ -e s_\ _'", "'_g)
    dbus -y com.victronenergy.settings / RemoveSettings "%[ $settings ]" &> /dev/null
}


# setSetting updates the dbus setting parameter
# the setting must already exist or the update will fail
# (the setting can not be created without knowing the data type(s))
#
# $1 is the new value
# $2 is the setting path

setSetting ()
{
	# don't do any work if install has already failed
	if $installFailed; then
		return
	fi

    dbus -y com.victronenergy.settings $2 SetValue $1 &> /dev/null
}

# move a setting from setup options or from previous dbus Setting
# $1 is the setup options path
# $2 is the old dbus path (has priority over setup option)
# $3 is the new dbus path
# dbus paths start with /Settings
# if specified, the setup option file must include a value
#   that value has priority over the old dbus parameter
#
# setup options can either contain a value or be a flag file
# for flag files, the file will be empty and the state of the option
# depends on the presence of the file (true) or absense of the file (false)
#
# Note: this function does NOT create or remove any old option or Setting
# use other functions or commands to do so

moveSetting ()
{
	# don't do any work if install has already failed
	if $installFailed; then
		return
	fi

    local setupOption="$1"
    local oldDbusPath=$2
    local newDbusPath=$3

    if [ ! -z "$oldDbusPath" ]; then
        oldSetting=$(dbus-send --system --print-reply=literal --dest=com.victronenergy.settings\
            $oldDbusPath com.victronenergy.BusItem.GetValue 2> /dev/null | awk '{print $3}')
    elif [ ! -z $setupOption ]; then
        if [ -f "$setupOption" ]; then
            oldSetting=$(cat "$setupOption")
            # flag file - old setting is true (1)
            if [ -z $oldSetting ]; then
                oldSetting=1
            fi
        # file did not exist - assume a false value for a flag file
        else
            oldSetting=0
        fi
    else
        oldSetting=""
    fi
    if [ ! -z $oldSetting ] && [ ! -z "$newDbusPath" ]; then
        dbus -y com.victronenergy.settings $newDbusPath SetValue $oldSetting &> /dev/null
    fi
}


================================================
FILE: HelperResources/EssentialResources
================================================
#!/bin/bash

# EssentialResources for SetupHelper
# contains a variables necessary for all setup helper scripts
#
# sourced from IncludeHelpers, packageManagerEnd.sh and reinstallMods

# get the full, unambiguous path to this script (and therefore to script that sourced it)
scriptDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )"
packageRoot="$( dirname $scriptDir )"
packageName=$( basename "$scriptDir" )

shortScriptName=$(basename "$scriptDir")/$(basename "$0")
fullScriptName="$scriptDir/$(basename "$0")"

if [ -f "/opt/victronenergy/version" ]; then
	venusVersion="$(cat /opt/victronenergy/version | head -n 1)"
else
	venusVersion=""
fi

installedVersionPrefix="/etc/venus/installedVersion-"
installedVersionFile="$installedVersionPrefix"$packageName

installedFilesDir="/etc/venus/installedModifications"
installedFilesList="$installedFilesDir/installedFiles"-$packageName
installedServicesList="$installedFilesDir/installedServices"-$packageName

# set up pointers to package files
#	based on the actual package for compatibility with older packages
pkgFileSets="$scriptDir/FileSets"
fileSet="$pkgFileSets/$venusVersion"
versionIndependentFileSet="$pkgFileSets/VersionIndependent"
# location of patch files
patchSourceDir="$pkgFileSets/PatchSource"
altOrigFileDir="$pkgFileSets/AlternateOriginals"

servicesDir="$scriptDir/services"


# LogHandler functions and variables

# enable logging to console
# scripts can disable logging by setting 
# logToConsole to false AFTER sourcing EssentialResources
logToConsole=true

runFromPm=false

# write a message to log file and console

logMessage ()
{
	# send to stdout if running from PackageManager
	#	it will include the message in its log file
	if $runFromPm ; then
		echo "$shortScriptName: $*"
	# to console
    elif $logToConsole ; then
        echo "$*"
    # to PackageManager log file if no other choices
    #	NOTE: PackageManager's logger service will manage the log file size
    #		however if it is not running /data could eventually fill up
	else
		echo "$shortScriptName: $*" | tai64n >> $logFile
    fi
}

# create log file and directory tree if it does not exist yet
logDir="/var/log/PackageManager"
logFile="$logDir/current"
if ! [ -e "$logDir" ]; then
	mkdir -p "$logDir"
	touch "$logFile"
	logMessage "creating log file and directory"
fi

# remove old log file if it still exists
oldLogFile="/var/log/SetupHelper"
if [ -e "$oldLogFile" ]; then
	rm -rf "$oldLogFile"
fi


# rc local file that calls reinstallMods
#	rcS.local avoids conflicts with mods that blindly replace /data/rc.local
rcLocal="/data/rcS.local"

# defined exit codes - must be consistent between all setup scripts and reinstallMods
# and PackageManager.py
EXIT_SUCCESS=0
EXIT_REBOOT=123
EXIT_RESTART_GUI=124
EXIT_ERROR=255	# unknown error
EXIT_INCOMPATIBLE_VERSION=254
EXIT_INCOMPATIBLE_PLATFORM=253
EXIT_FILE_SET_ERROR=252
EXIT_OPTIONS_NOT_SET=251
EXIT_RUN_AGAIN=250
EXIT_ROOT_FULL=249
EXIT_DATA_FULL=248
EXIT_NO_GUI_V1=247
EXIT_PACKAGE_CONFLICT=246
EXIT_PATCH_ERROR=245


# directory that holds script's options
# options were removed from the script directory so they are preserved when the package is reinstalled
setupOptionsRoot="/data/setupOptions"
setupOptionsDir="$setupOptionsRoot"/$packageName

# packages managed by SetupHelper
packageListFile="/data/packageList"

qmlDir=/opt/victronenergy/gui/qml


# setInstallFailed sets flags to prevent further install steps
#	and insure the package is uninstalled completely
#
#	$1 indicates the reason for the failure and will evenutally be uused
#	report the failure reason when exiting the script
#
#	any remaining paremeters are passed to logMessage
#		and also saved in installFailMessage for others to use
#	the message is also sent to stderr if not running from the command line
#		this allows PackageManager to report the full reason for failure
#
#	a setup script can be run from the console, or from another script or program (unattended)
#	when running from the console
#		setInstallFailed will report errors and return to the caller
#
#	if running unattended and if the script action is INSTALL
#		during the precheck period (before any system modification)
#			setInstallFailed will EXIT WITHOUT RETURNING TO THE CALLER !!!!!
#		after the precheck period, system modifications may have been made so
#			the scriptAction is changed to UNINSTALL so the modifictions can be reversed
#	otherwise, setInstallFailed just logs the error
#
# installFailed is set here so that additional install operations will not be performed

installFailed=false
installExitReason=$EXIT_ERROR
uninstallExitReason=$EXIT_ERROR
installFailMessage=""
installPreChecks=true
installFailCount=0
uninstallFailed=false

setInstallFailed ()
{
	local reason

	(( installFailCount += 1 ))
	if [ ! -z "$1" ]; then
		reason=$1
	# no reson specified - use the generaic error exit code
	else
		reason=EXIT_ERROR
	fi
	installFailMessage="${@:2}"
	if [ ! -z "$installFailMessage" ]; then
		# output error to stderr if run from PackageManager
		if $runFromPm ; then
			echo "$installFailMessage" >&2
		# otherwise output to log (and/or stdout/console)
		else
			logMessage "ERROR: $installFailMessage"
		fi
	fi

	if [ $scriptAction == 'UNINSTALL' ]; then
		uninstallExitReason=$reason
		uninstallFailed=true
	else
		installExitReason=$reason
		installFailed=true
	fi
	if [ $scriptAction == 'INSTALL' ]; then
		# after "pre-checks" system mofifications may already have occured
		#	so an uninstall needs to follow the install
		if ! $installPreChecks ; then
			scriptAction='UNINSTALL'

		# during "pre-checks" failures occur before any system mofifications
		#	if no user interaction EXIT NOW - DO NOT RETURN TO THE CALLER !!!!!
		elif ! $userInteraction ; then
			exit $installExitReason
		fi
	fi
}

# set global machine type
if [ -f /etc/venus/machine ]; then
	machine=$(cat /etc/venus/machine)
else
	machine=""
	setInstallFailed $EXIT_INCOMPATIBLE_PLATFORM "can't determine Venus device type"
fi

# make sure rootfs is mounted R/W & and resized to allow space for replacement files
#	arbitrary minimum size of 3 MB
# this needs to be called before root fs mods are made.
#	CommonResources calls this but if you source a subset of helper resources
#	that script needs to find a place to call updateRootToReadWrite

updateRootToReadWrite ()
{
	if ! $installFailed; then
		rootMinimumSize=3
		availableSpace=$(df -m / | tail -1 | awk '{print $4}')

		# remount read-write
		if (( $(mount | grep ' / ' | grep -c 'rw') == 0 )); then
			# only remount read-write for CCGX
			if [ "$machine" == "ccgx" ]; then
				if [ -f /opt/victronenergy/swupdate-scripts/remount-rw.sh ]; then
					logMessage "remounting root read-write"
					/opt/victronenergy/swupdate-scripts/remount-rw.sh
				fi
			# remount and resize for other platforms
			elif [ -f /opt/victronenergy/swupdate-scripts/resize2fs.sh ]; then
				/opt/victronenergy/swupdate-scripts/resize2fs.sh
				availableSpace=$(df -m / | tail -1 | awk '{print $4}')
				logMessage "remounting root read-write and resizing - $availableSpace MB now available"
			fi
			# check to see if remount was successful
			if (( $(mount | grep ' / ' | grep -c 'rw') == 0 )); then
				setInstallFailed $EXIT_ROOT_FULL "ERROR: unable to remount root read-write - can't continue"
			fi
		# root already read-write, attempt to resize if space is limited (CCGX can't resize)
		elif (( $availableSpace < $rootMinimumSize )) && [ "$machine" != "ccgx" ]; then
			if [ -f /opt/victronenergy/swupdate-scripts/resize2fs.sh ]; then
				/opt/victronenergy/swupdate-scripts/resize2fs.sh
				availableSpace=$(df -m / | tail -1 | awk '{print $4}')
				logMessage "resized root - $availableSpace MB now available"
			fi
		fi
	fi
	if ! $installFailed; then
		# make sure the root partition has space for the package
		if (( $availableSpace < $rootMinimumSize )); then
			setInstallFailed $EXIT_ROOT_FULL "no room for modified files on root ($availableSpace MB remaining) - can't continue"
		fi
	fi
}


# convert a version string to an integer to make comparisions easier
# the Victron format for version numbers is: vX.Y~Z-large-W
# the ~Z portion indicates a pre-release version so a version without it is "newer" than a version with it
# the -W portion has been abandoned but was like the ~Z for large builds and is IGNORED !!!!
#	large builds now have the same version number as the "normal" build
#
# the version string passed to this function allows for quite a bit of flexibility
#	any alpha characters are permitted prior to the first digit
#	up to 3 version parts PLUS a prerelease part are permitted
#		each with up to 4 digits each -- MORE THAN 4 digits is indeterminate
#	that is: v0.0.0d0  up to v9999.9999.9999b9999 and then v9999.9999.9999 as the highest priority
#	any non-numeric character can be used to separate main versions
#	special significance is assigned to single caracter separators between the numeric strings
#		b or ~ indicates a beta release
#		a indicates an alpha release
#		d indicates an development release
# 		these offset the pre-release number so that b/~ has higher numeric value than any a
#			and a has higher value than d separator
#
# a blank version or one without at least one number part is considered invalid
# alpha and beta seperators require at least two number parts
#
# returns 0 if conversion succeeeded, 1 if not
# the value integer is returned in $versionNumber
# a status text string is returned in $versionStringToNumberStatus
#	and will include the string passed to the function
#	as well as the converted number if successful and the type of release detected
#		or an error reason if not
#


function versionStringToNumber ()
{
	local version="$*"
	local numberParts
	local versionParts
	local numberParts
	local otherParts
	local other
	local number=0
	local type='release'

	# split incoming string into
	# an array of numbers: major, minor, prerelease, etc
	# and an array of other substrings
	# the other array is searched for releasy type strings and the related offest added to the version number
	
	read -a numberParts <<< $(echo $version | tr -cs '0-9' ' ')
	numberPartsLength=${#numberParts[@]}
	if (( $numberPartsLength == 0 )); then
		versionNumber=0
		versionStringToNumberStatus="$version: invalid, missing major version"
		return 1
	fi
	if (( $numberPartsLength >= 2 )); then
		read -a otherParts <<< $(echo $version | tr -s '0-9' ' ')
	
		for other in ${otherParts[@]}; do
			case $other in
				'b' | '~')
					type='beta'
					(( number += 60000 ))
					break ;;
				'a')
					type='alpha'
					(( number += 30000 ))
					break ;;
				'd')
					type='develop'
					break ;;
			esac
		done
	fi

	# if release all parts contribute to the main version number
	#	and offset is greater than all prerelease versions
	if [ "$type" == "release" ] ; then
		(( number += 90000 ))
	# if pre-release, last part will be the pre release part
	#	and others part will be part the main version number
	else
		(( numberPartsLength-- ))
		(( number += 10#${numberParts[$numberPartsLength]} ))
	fi
	# include core version number
	(( number += 10#${numberParts[0]} * 10000000000000 ))
	if (( numberPartsLength >= 2)); then
		(( number += 10#${numberParts[1]} * 1000000000 ))
	fi
	if (( numberPartsLength >= 3)); then
		(( number += 10#${numberParts[2]} * 100000 ))
	fi

	versionNumber=$number
	versionStringToNumberStatus="$version:$number $type"
	return 0
}



# compares two version strings
#
# missing verions are treated as 0
#
# returns 0 if they are equal
# returns 1 if the first is newer than the second
# returns -1 if the second is newer than the first

function compareVersions ()
{
	local versionNumber2

	if [ -z $2 ]; then
		versionNumber2=0
	else
		versionStringToNumber $2
		versionNumber2=$versionNumber
	fi
	if [ -z $1 ]; then
		versionNumber=0
	else
		versionStringToNumber $1
	fi

	if (( versionNumber == versionNumber2 ));then
		return 0
	elif (( versionNumber > versionNumber2 ));then
		return 1
	else
		return -1
	fi
}


================================================
FILE: HelperResources/IncludeHelpers
================================================
#!/bin/sh

# this script sources helper Resources into the setup script
#
# for backward compatibility, CommonResources in the SetupHelper directory
#	links to this file, not CommonResources
#	CommonResources previously sourced the other files
#	now sourcing all resource files is done from here
#
# only the helper files located is the SetupHelper directory are used
#	previous versions chose between this and a helper file set in the package directory
#	but changes in SetupHelper to use a local copy of patch made this not possible
#
# this script should be sourced in the setup script before any other activities

pkgDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )"
pkgRoot="$( dirname "$pkgDir")"
pkgName=$( basename $pkgDir )
helperResourcesDir="$pkgRoot/SetupHelper/HelperResources"
logDir="/var/log/PackageManager"
logFile="$logDir/current"

if ! [ -e "$helperResourcesDir" ]; then
	echo "$pkgName: helper files not found - can't continue" | tee -a "/data/log/SetupHelper"
	exit 1
fi

# if we get here, helper files were located - source the files
helperFileList=( EssentialResources ServiceResources DbusSettingsResources )
for file in ${helperFileList[@]}; do
	if [ -f "$helperResourcesDir/$file" ]; then
		source "$helperResourcesDir/$file"
	else
		echo "$pkgName: helper file $file not found - can't continue" | tee -a "$logFile"
		exit 1
	fi
done

# now transfer control to CommonResoures - it may not return !
if [ -f "$helperResourcesDir/CommonResources" ]; then
	source "$helperResourcesDir/CommonResources"
else
	echo "$pkgName: helper file CommonResources not found - can't continue" | tee -a "$logFile"
	exit 1
fi




================================================
FILE: HelperResources/LogHandler
================================================
# dummy file to prevent failure with older package setup scripts

# logging is now in EssentialResources


================================================
FILE: HelperResources/ServiceResources
================================================
#!/bin/bash

#!/bin/bash
# ServiceManager for SetupHelper
# contains a functions to install and remove a package's service
#
# If an active copy of the service already exists, the run and log/run files are updated
# ONLY if there are changes, then the service and/or the logger will be restarted.
# This leaves other files managed by supervise untouched.
#
# in Venus OS starting with v2.90~3, /service is mounted as a tmpfs (RAM disk)
# /opt/victronenergy/service is copied to /service ONLY at boot time
# so new services need to be copied to /opt/victronenergy/service for boot processing
# AND to /service so they run immediately
#
# svc -u /service/<service name> starts a service that is not already running
# svc -d /service/<service name> stops a service and will not restart
# these are "temporary" and don't survive a system boot
# svc -t /service/<service name> sends the service a TERM command
#	if the service was up at the time, it restarts
#	if the service was down at the time, it is NOT started
#
# the /service/<service name>/down flag file controls the state of a service at boot time:
#	if the file exists, the service won't start automatically at boot or when created
#	if the file does not exist, the service will start at boot or immediately when it is created
#
# if the services manager (svscan) is not up, or the real service directory does not yet exist
#	some steps will be skipped to avoid errors in calling daemontools functions
#
# more info here:
#	https://cr.yp.to/daemontools/svc.html
#	https://cr.yp.to/daemontools/supervise.html
#	https://cr.yp.to/daemontools/svstat.html
#	https://cr.yp.to/daemontools/svok.html

# storage for services
#	/service is the location where services run from
#	and is mounted as a temp FS
#
# contents of serviceDir is copied to /service during boot
#	but at no other time, so when services are added
#	they need to be copied to both locations
serviceDir="/opt/victronenergy/service"


# check to see if services manager is running
svscanIsUp ()
{
	pgrep -lx svscan &> /dev/null
	if (( $? == 0 )) ; then
		return 0
	else
		return 1
	fi
}

# check to see if named service is up
serviceIsUp ()
{
	if ! svscanIsUp ; then
		return 1
	elif [ ! -e "/service/$1" ]; then
		return 1
	elif [ $(svstat "/service/$1" | awk '{print $2}') == "up" ]; then
		return 0
	else
		return 1
	fi
}


#
# removeService cleanly removes the service
#
# When uninstalling from within PackageManager,
#	the PackageManager service is only removed from servicesDir and from installedServicesList
#	the copy in /services is marked so it will not run again after PackageManager exits


removeService ()
{
    # no service specified
    if (( $# < 1 )); then
        return
    fi
	local serviceName="$1"
	
	local shutdownService=true
	if [ "$serviceName" == "PackageManager" ] && $runFromPm ; then
		shutdownService=false
	fi

    if [ -e "$serviceDir/$serviceName" ]; then
        logMessage "removing $serviceName service"
		# set flag so PackageManager won't restart after a reboot
		if ! $shutdownService ; then
			logMessage "$serviceName service will remain running but will not run again"
			touch "/service/$serviceName/down"
			# if PackageManager is running, mark it so it will not restart when it exits
			if serviceIsUp $serviceName ; then
				svc -o "/service/$serviceName"
			fi
		# stop the service if it is currently running
		elif serviceIsUp $serviceName ; then
			svc -d "/service/$serviceName"
		fi
		if $shutdownService && serviceIsUp "$serviceName/log" ; then
			svc -d "/service/$serviceName/log"
		fi

		# supervise processes may hang around after removing the service so save info and kill them after removal
		pids=""
		while read -u 9 line; do
			read s uid pid ppid vsz rss tty stime time cmd blah <<< "$line"
			if [ $cmd == 'supervise' ]; then
				pids+="$pid "
			elif [ $cmd == 'multilog' ]; then
				pids+="$ppid "
			fi
		done 9<<< $(ps -lw | grep $serviceName)

		# remove the service directory
		rm -rf "$serviceDir/$serviceName"

		# /service is mounted as tmpfs so the service needs to be removed from /service also
		# when uninstalling SetupHelper from withing PackageManager, do NOT delete the running service !
		#	(after the next reboot, /service/PackageManager won't becopied from serviceDir)
		#	(logger remains running until the next boot)
		if $shutdownService ; then
			rm -rf "/service/$serviceName"

			# also kill the supervise processes
			kill $pids
		fi
    fi

	# remove service from installed services list
	if [ -f "$installedServicesList" ]; then
		grep -v "$serviceName" "$installedServicesList" | tee "$installedServicesList" > /dev/null
	fi
}


# installService adds the service to the /service directory or updates an existing one
#
# If the service does not yet exist, it is created
# If the service already exists, installService will
# update the service files then restart the service and the logger


# The service usually starts automatically within a few seconds of creation.
# installService waits 10 seconds to see if the service starts on its own
#	if not, it will be started
#
# The service may contain a "down" flag file. If present, the service won't be started.
# This allows the service to be started manually later.
# If the down flag is present the service will not start at boot.
#
#
# $1 is the service name -- that is the name of the service in /service
# 	the package name will be used as the service name if not specified on the command line
#
# $2 is the directory in the script directory to be copied to the service in /service
#	(this includes the run and control (down) files)
# 	the default is 'service' in the package directory
#
# for most packages with one service, the defaults are fine
# however if a package needs to install more than one service
#	then the service name and directory must be specified
#	installService "PackageManager" "servicePM"
#	installService "SetupHelper" "serviceSH"
#	servicePM/run would include a call to /data/SetupHelper/PackageManager.py
#	serviceSH/run would include a call to /data/SetupHelper/SetupHelper.sh

installService ()
{
	# don't do any work if install has already failed
	if $installFailed; then
		return
	fi

	local serviceName=""
    if (( $# >= 1 )); then
        serviceName=$1
	else
		serviceName=$packageName
    fi

    local servicePath=""
    if (( $# >= 2 )); then
        servicePath="$scriptDir/$2"
	elif [ -e "$servicesDir/$serviceName" ]; then
		servicePath="$servicesDir/$serviceName"
	elif [ -e "$scriptDir/service" ]; then
		servicePath="$scriptDir/service"
    fi
	
    # no service to install
    if [ ! -e "$servicePath" ]; then
		setInstallFailed $EXIT_ERROR "service $service not found - can't continue"
		return
    fi

    if [ -L "$serviceDir/$serviceName" ]; then
        logMessage "removing old $serviceName service (was symbolic link)"
		removeService $serviceName
    fi

	# add service to the installed services list (used for uninstallAll)
	# do this before actually modifying things just in case there's an error
	#	that way the uninstall is assured
	echo "$serviceName" >> "$installedServicesList"

    # service not yet installed, COPY service's directory (run files) to the service directory(s)
	rm -rf "$serviceDir/$serviceName"
	cp -R "$servicePath" "$serviceDir/$serviceName"
	local loggerOk=false
	if [ -e "$serviceDir/$serviceName/log" ] && ! [ -e "$serviceDir/$serviceName/log/down" ]; then
		loggerOk=true
	else
		logMessage "$serviceName logger disabled (down flag) or does not exist"
		loggerOk=false
	fi

    if [ ! -e "/service/$serviceName" ]; then
        logMessage "installing $serviceName service"
		cp -R "$servicePath" "/service/$serviceName"

		# if down flag is NOT set, check every second for service to start automatically
		# then start it here if it is not running after 10 seconds
		if [ -f "$serviceDir/$serviceName/down" ]; then
			logMessage "$serviceName not (re)started - must be started manually (down flag set)"
		elif ! svscanIsUp ; then
			logMessage "services manager (svscan) not yet up - $serviceName should start automatically later"
		else
			local delayCount=10
			local serviceRunning=false
			local loggerRunning=false
			while (( $delayCount > 0 )); do
				if serviceIsUp $serviceName ; then
					serviceRunning=true
				fi
				if serviceIsUp $serviceName/log ; then
					loggerRunning=true
				fi
				if $serviceRunning && ( $loggerRunning || ! $loggerOk ); then
					break
				fi
				# only report wait once
				if (( delayCount == 10 )); then
					echo "waiting for $serviceName service to start"
				fi
				sleep 1
				(( delayCount-- ))
			done
			if $loggerOk ; then
				if $loggerRunning ; then
					logMessage "$serviceName logger running"
				else
					logMessage "starting $serviceName logger"
					svc -u "/service/$serviceName/log"
				fi
			fi
			if $serviceRunning; then
				logMessage "$serviceName service is running"
			else
				logMessage "starting $serviceName service"
				svc -u "/service/$serviceName"
			fi
		fi

    # service already installed - only copy changed files, then restart service if it is running
    else
        if [ -f "/service/$serviceName/log/run" ]; then
            cmp -s "$servicePath/log/run" "/service/$serviceName/log/run" > /dev/null
            if (( $? != 0 )); then
				logMessage "updating $serviceName logger run file"
				cp "$servicePath/log/run" "/service/$serviceName/log/run"
            fi
			if [ -e "$servicePath/log/down" ]; then
				svc -d "/service/$serviceName/log"
			else
				rm -f "/service/$serviceName/log/down"
				if serviceIsUp "$serviceName/log" ; then
					echo "restarting $serviceName logger"
					svc -t "/service/$serviceName/log"
				else
					logMessage "starting $serviceName logger"
					svc -u "/service/$serviceName/log"
				fi
			fi
        fi
        if [ -f "/service/$serviceName/run" ]; then
            cmp -s "$servicePath/run" "/service/$serviceName/run" > /dev/null
            if (( $? != 0 )); then
				logMessage "updating $serviceName run file"
				cp "$servicePath/run" "/service/$serviceName/run"
            fi
			if [ -e "$servicePath/down" ]; then
				svc -d "/service/$serviceName"
			else
				rm -f "/service/$serviceName/down"
				if serviceIsUp $serviceName ; then
					echo "restarting $serviceName service"
					svc -t "/service/$serviceName"
				else
					logMessage "starting $serviceName service"
					svc -u "/service/$serviceName"
				fi
			fi
        fi
    fi
}


================================================
FILE: HelperResources/VersionResources
================================================
# dummy file to prevent failure with older package setup scripts

# version resources now in EssentialResources

================================================
FILE: PackageDevelopmentGuidelines.md
================================================
# SetupHelper package development

**Kevin Windrem kwindrem@icloud.com**

**updated for v6.0**

This document provides guidance in developing a package suitable for
PackageManager management. When this guide is followed, PackageManager
should be able to download the package from GitHub or from media
inserted in the GX device and install it (bring it into operation under
Venus OS).

PackageManager is part of the SetupHelper package. It monitors GitHub
for package updates, downloads and installs them automatically (if
desired). It also provides a mechanism to add and remove packages from
those PackageManager will manage. The user interface for PackageManager
is a set of menus in the Venus OS GUI located at Menu / Settings /
PackageManager.

SetupHelper includes a set of utilities intended to handle the bulk of
package installation and uninstallation and also insures the package is
reinstalled following a firmware update. A Venus OS update will restore
any modified files to factory defaults, so any modifications must be
reinstalled following the Venus OS update.

There are a few basic requirements for a package to be recognized by
PackageManager:

1.  The package name must conform to unix file naming convention.

    To be most compatible, limit the package name to Upper and lower case
    alpha characters or numbers. **No spaces are allowed.**

1.  The package must contain a file named `version`. Specifics of the
    version are described later.

2.  The package should contain a file named `setup`. This should be an
    executable shell script that will install, uninstall or reinstall
    the package. SetupHelper includes unix bash shell script
    extensions that must be included in the setup script in order to
    be properly managed for reinstall after a Venus OS update. More
    details on the SetupHelper extension later.

3.  The package should contain a file named `gitHubInfo` which should
    contain the Git Hub user name and default branch separated by `:`
    E.g., `kwindrem:latest`

4.  The primary purpose of the setup script is to install modified files
    to the Venus OS file system. It is important that the package
    setup script is also written to **uninstall** the package,
    restoring any modified files to the factory settings. SetupHelper
    provides a number of utilities for this.

5.  In order for PackageManager to download the package from the
    internet, it must be contained in a GitHub repository. Any GitHub
    branch, tag or release may be referenced as long as it is valid.
    The suggested tag is `latest` which should point to the most
    recent released version. Additional tags matching a version number
    may be created in order to allow download of specific versions.
    Tags or branches such as `beta`, etc may also be created.
    PackageManager can also use branch names for specific downloads.

6.  The package script is run automatically only if a reinstall is
    required. Code that needs to run constantly or once each boot
    should use a service.

7.  A package may modify (replace) files in the Venus OS to enhance or
    change behavior. These replacement files should be stored in "file
    sets" one for each Venus OS version so that the package can be
    installed regardless of the Venus OS version currently running.
    More about file sets later.

8.  A package may optionally restrict its operation to a range of Venus
    OS versions.

9.  A package may also restrict its operation to a specific platform.
    Currently, the only restriction is for RaspberryPi platforms.

10. The package may include a ReadMe file describing the package and how
    to install it.

11. The package may contain a change log indicating what changed in each
    version.

12. The package may modify GUI files (specifically GUI v1 currently).
    Normally these modifications are essential to the functionality
    provided. If GUI v1 is not present on the system, the install will
    fail. This can be overridden with the `GUI_V1_NOT_REQUIRED` flag
    file in the package directory.

13. Setup scripts should execute as rapidly as possible. Time spent
    inside a setup script will delay other activities such as
    installing other packages and Package Manager will appear to be
    hung as there is no status update during lengthy operations. For
    this reason: **Any time-consuming activities such as installing
    files from the internet or compiling code must be offloaded to a
    secondary process.**

14. Dependencies on other packages should be identified in the
    packageDependencies file, described later.

## SetupHelper

SetupHelper oversees the installation, uninstallation or reinstallation
of the package. The package setup script must call ("source")
**IncludeHelpers**, a shell script extension before doing any work to
install or uninstall the package:

```bash
#### following line incorporates helper resources into this script

source "/data/SetupHelper/HelperResources/IncludeHelpers"

#### end of lines to include helper resources
```

IncludeHelpers sources the other helper resources.

These extensions validate the package for the current Venus OS version
and platform and builds a file set for the current Venus OS version if
necessary.

Beginning with SetupHelper v6.0, file and service and dBus Setting
installation is further simplified. These operations will be based on
various list(s) and contents of the services directory located in the
package directory.

Some modifications, such as adding lines to /u-boot/config.txt for
Raspberry PIs will still require code in the setup script, but in
general **installFiles**, **installServices** and **addDbusSettings**
functions will handle the bulk of the activities. These can be called
from the setup script or more conveniently, triggered when calling
**endScript**.

Including the following line in the setup script BEFORE sourcing
IncludeHelpers triggers a default prompt for install or uninstall then
proceeds with the install/uninstall operations:

```bash
standardPromptAndActions='yes'
```

In that case, InstallHelpers never returns to the setup script.

If the `standardPromptAndActions` is not set, the helper resources will
then hand control back to the setup script. A prompt for user input is
then necessary followed by any processing and installation that can't be
handled by the list-based functions mentioned above.

After any special processing has been performed, the script should call
the '**endScript**' function (in CommonResources) in order to exit the
script with the proper exit codes. These exit codes are necessary for
proper behavior of PackageManager and the automated reinstall operations
performed at system boot when the Venus OS is updated.

Parameters passed to endScript triggers automatic install/uninstall.
(described later) For example:

```bash
endScript INSTALL_FILES INSTALL_SERVICE ADD_DBUS_SETTINGS
```

**genericSetupScript** located in the SetupHelper directory will install
any package that does not require in-line prompting, install or
uninstall code. Simply link to this setup script in that package's
directory or copy it there.

## Patched files

Package installation has historically replaced files to modify content
of the active file. Beginning with v8.0, SetupHelper will attempt to
patch an active file in the fileListPatched list rather that replacing
it. This allows multiple packages to modify the same file.

Files to be patched require entries in the PatchSource directory in the
FileSets directory. They also require an entry in the fileListPatched
list. A patch (...patch) file is used during the patch process,
operating on the active file.

The original, unmodified active file(...orig) along with a modified
version are needed to create the patch file. If these files are present
in the PatchSource directory, updatePackage will attempt to create a
patch file, or update it if the orig and modified files have been
changed making the .patch file obsolete.

**diff -u** is used to create the patch file.

A ...patchOptions file can override the default -u option. For example
if the patchOptions file is **-U 0** the patch file will not have any
context lines. If patchOptions includes **MANUAL**, then
**updatePackage** will not attempt to create a patch file. In this case
the patch file will need to be created manually. E.g., with:

```bash
diff -u foo-1.orig foo-1 > foo-1.patch
```

The patch file then needs to be located in the PatchSource directory.

On the GX device, helper resources use the .patch file to create a
patched file. These are stored in a temp directory created by **mktemp**
and can be referenced with $patchedFiles/foo-1.patch.

Some modifications may not be suitable for all Venus OS versions. The
patch mechanism allows multiple patch files. E.g.:

```bash
PageSettings.qm-1.patch, PageSettings.qm-2.patch
```

Any unique string after the - is an acceptable suffix for the set of
patch files.

During installation, each patch file will be tested and the first one to
succeed is used to patch the active file and unpatch it later during
uninstall.

During installation, CommonResources attempts to patch the current
active file, then reverse patch it to insure that original can be
recreated during a future uninstall. If these succeed, the install is
allowed to continue.

During the uninstall process, the active file replaced by the .orig file
if no other packages have modified the file. However, if there are other
package modifications still present in the active file, it is "reverse
patched" to restore the file to it's state before the package was
installed. This would remove the modifications for this package while
preserving modifications from other packages.

There is a slight possibility that this "reverse patch" could fail. If
this occurs, the active file is replaced by the .orig file in order to
prevent a system crash due to a missing or incorrectly patched active
file. **Modifications from other packages are lost, requiring them to be
reinstalled.**

There are restrictions that package authors need to keep in mind. These
restrictions are due to "context" created for the modification. These
are lines prior to and after the modification that are the same with and
without the modification. **patch** uses these to locate the proper
place in the current active file to apply the patch.

1.  Modifications from multiple packages must not overlap. Even
    modifications that occur at the exact same place in the file might
    not produce a patch file that succeeds with and without the other
    package's modifications because the context lines won't match.

2.  Modifications at the very beginning or end of a file may not produce
    a usable patch file. Again, because there is insufficient context.

It is important that the author identify other packages that modify the
same files and test installation and uninstallation of all packages
involved, *and* in different install/uninstall orders.

> [!NOTE] 
> The patch utility provided with Venus OS is part of BusyBox
> and has limitations that prevent its use by SetupHelper. Therefore, a
> more fully functioned version is included with SetupHelper beginning
> with v8.0 and is used by HelperResources.

## Setup script command line options

CommonResources checks for optional command line parameters before
passing control to the body of the setup script.

- **reinstall** indicates this is to be a reinstall. Installed version
  is compared to the version in the package directory and skips
  reinstall if they match. User prompting is skipped. (These version
  checks are now redundant since **reinstallMods** which controls
  package reinstalls following a Venus OS update also check versions
  before calling the package setup script.)

- **install** indicates that the prompts should be skipped and the
 installation begun with stored options (more about this later).
 **uninstall** indicates the prompts should be skipped and the package
 uninstalled.

- **auto** silences all console messages. Progress of the setup is
  logged. Without the **auto** option progress is also written to the
  console.

- **deferReboot** instructs endScript to skip the automatic system
  reboot and return indicating a future reboot is needed.

- **deferGuiRestart** is as above but for automatic GUI restarts.

Without any options, helper resources assumes a user has run the script
and sets up the environment to prompt for user input to control
additional execution.

When the setup script regains control following CommonResources, the
variable 'scriptAction' will be set to either NONE, INSTALL or
UNINSTALL. NONE indicates that the script should prompt for user input
to control further execution. These prompts should be skipped if
scriptAction is either INSTALL or UNINSTALL. Note that if an install
action fails, scriptAction will be set to UNINSTALL, so scriptAction
must be tested again after the install section and perform the uninstall
operations. This prevents a partial install from disrupting the system
operation. Note that this behavior is not automatic and must be written
into all package setup scripts.

Venus OS is a "dual root fs" system. That is, the operating system and
executable parts of the system reside on one of two root partitions. One
partition is active and the other inactive. A Venus OS firmware update
installs files to the inactive partition. Then when the update has been
verified, the active and inactive portions are swapped and the system
rebooted to execute the updated code. If the update is unsuccessful, the
swap does not occur and the old executable files continue to run. This
prevents a partial or corrupted firmware update from bricking the
system. A third partition (/data) holds any persistent information
(settings, etc).

Packages are stored in the data partition so they survive a Venus OS
firmware update, however any system files will be overwritten.

At boot time, the **reinstallMods** script is called. Starting with
SetupHelper 8.0, **reinstallMods** only installs the PackageManager
service. It sets a flag: /etc/venus/REINSTALL_PACKAGES then exists.
**PackageManager** then tests this flag file and installs the ALL
packages, including the remainder of SetupHelper. This was done to avoid
so that all of PackageManager's pre-install checks to be made prior to
installing a package. Plus some of the packages available now take
several minutes to install resulting in a lag to install all packages
after a Venus OS update. Now, status is shown in **PackageManagers**'s
menus and in it's log file.

When packages are reinstalled, the package setup script is called with
the **reinstall**, **auto**, **deferReboot** and **deferGuiRestart**
options. **PackageManager** will then trigger a system reboot or GUI
restart if any package setup scripts have requested those actions.

**PackageManager** acts on requests for system reboot or GUI restarts
when an install or uninstall is triggered from it's menu. A user choice
to defer reboots and GUI restarts is then provided.

A package may sometimes need to manually modify certain files because
the automatic mechanisms are too general. For example, adding device
tree overlays to `/u-boot/config.txt` must be done in a way that it does
not disturb what's currently in the file. All the automatic install
mechanism can do is replace the existing file. Code for such
modifications goes in the `setupAction == 'INSTALL'` section of code and
any restoration code would still go in the `setupAction == 'UNINSTALL'`
section.

Setup scripts written prior to SetupHelper v6.0 will continue to
function. even if the `INSTALL_...` options are set when calling
endScript. endScript will attempt to repeat install and uninstall
operations but no harm will be done (other than taking additional
time).

## Automated install

Beginning with SetupHelper v6.0, install and uninstall can usually be
handled within CommonResources. Prior to SetupHelper v6.0, the setup
script needed code to install and uninstall every modified file or
service using the utilities described in the next section. Calls to
install and uninstall services was also needed.

Installs use "file lists" to install modified files. Each file
modified is saved in a modified files list which is then used to
uninstall the package.

Refer to the section on File Sets below for more details on file
lists.

All services to be installed by the automatic processes must be
located in the services directory. There is no services list since the
directory provides the necessary information. An installed services
list is created to allow for automatic uninstall.

`/data/< package name >/services`

Services are still directories with run and log/run files as before.
Any service directories found in services will be automatically
installed. Automatically installed services are added to an installed
services list and will be automatically uninstalled using that list.

The name of the directory within services will determine the service
name. E.g., the following service directory will create the
PackageManager service

`/data/SetupHelper/services/PackageManager`

Prior to SetupHelper v6.0, services were located in the package
directory. The service name was determined by the package name, or
specified on the installService line. These services will NOT be
automatically installed, so their service directories are moved to
services and named appropriately.

The modified files and services list are located in /etc/venus.

`/etc/venus/installedModifications/installedFiles-<package name>`

`/etc/venus/installedModifications/installedServices-<package name>`

This location was chosen because it is removed on a Venus OS update.
Running the setup script to uninstall a package will therefore do
nothing as expected. If the lists were stored in /data, then an
uninstall would attempt to uninstall the files and services again.
Generally, that isn't a problem but really should not happen.

The package is uninstalled by walking through the installed... lists
to restore the original files. Generally, those calls in the setup
script are no longer necessary.

## SetupHelper utilities

SetupHelper provides a set of utilities to simplify the installation of
modified files (called a "replacement") into the active root file system. It pulls the correct
replacement file from a "file set" for the current Venus OS version,
moves the original out of the way and installs the replacement file. On
uninstall, the original file is moved back to the active location (file
name) leaving the system in an unmodified state.

> [!NOTE]
> Starting with v6.0, these utilities may be of less use but are still
included.

**updateActiveFile** is a function that installs a replacement file as
described above. Typically, the replacement file has the same name as
the original so the simple form of the command can be used. For example,
to replace a GUI file:

`updateActiveFile "/opt/victronenergy/gui/qml/main.qml"`

If however, the replacement file has a different file name, a second
form is used. This may be necessary if the setup script has to build or
modify the replacement file.

`updateActiveFile "$scriptDir/foo.qml" "/opt/victronenergy/gui/qml/main.qml"`

**restoreActiveFile** is a function that undoes the above operation and
is called during uninstall with the same file names as were used with
updateActiveFile during install:

`restoreActiveFile "/opt/victronenergy/gui/qml/main.qml"`

**backupActiveFile** is a function that creates the .orig file used by
restoreActiveFile but does not update the active file. It is preferable
to make modifications in the setup script to a temp file then use
**updateActiveFile** as described above but this is not always possible.

Use only when something modifies the active file in place:

`backupActiveFile "/etc/pointercal"`

`ts_calibrate # modifies /etc/pointercal directly`

**installService** is a function that installs and starts a dameon
service. The service will be placed in the / service directory (or
/opt/victronenergy/service for v2.80 or later). A folder in the package
folder named 'service' must contain the files that will end up in
/service under the service name

`installService FooService`

Will copy the service directory from the package directory into
/service/FooService

**removeService** is a function that removes the service which is
necessary during an uninstall to restore the system to factory.

`removeService FooService`

**logMessage** is a function that will log anything of interest.
Messages are either sent to stdout or to the PackageManager log file:
log file: /var/log/PackageManager/current.
Logging is encouraged as it helps debug systems in
the field (and while developing the package). Without the **auto**
option on the call to the setup script, these messages are also output
to the console.

To conform to Victron guidelines, messages are sent to stdout in
all but a few unavoidable situations:
running the script in **auto** from the command line (discouraged)
**reinstallMods** or **blindInstall** **blindUninstall**.

When scripts are run from PackageManager, stdout is collected
and forwarded to the log using python's logging.info () method.
This way, a the informaiton is persistant for debugging.

When scripts are run from the command line, any messages
appear on the console but are **NOT logged**.

`logMessage "this text will end up in the log"`

**endScript** Function to finish up, prompt the user (if not
reinstalling) and exit the script. (Details are described below.)

*The following functions simplify the task of getting user input.*

**standardActionPrompt** displays a menu of actions and asks the user to
choose

- It sets scriptAction accordingly and returns

- It also handles displaying setup and package logs then asks for an
 action again

- It also handles quitting with no action - the function *exits the
 shell script* without returning in this case

- The basic action prompt includes install, reinstall, quit, display
 logs (2 choices)

- A reinstall option is enabled if the optionsSet option exists

- When reinstall is enabled, selecting install, returns a scriptAction
  of NONE indicating additional prompting may be needed to complete the
  install

- At the end of these prompts, the main script should set scriptAction
  to INSTALL

- If reinstall is selected, the script action is set to INSTALL and the
  main script should then skip additional prompts and allow options set
  previously to control the install

**yesNoPrompt "prompt "**

- Asks the user to answer yes or no to the question

- Any details regarding the question should be output before calling
  yesNoPrompt

- **yesNoPrompt** sets **$yesResponse** to true if the answer was 0 if
  yes and 1 for no so that the return code can be checked rather than
  checking $yesResponse:

    ```bash
    if yesNoPrompt "do it (y/n)?" ; then
      do stuff for yes response
    else
      do stuff for now response
    fi
    ```

A set of utilities manages dbus Settings: creating, removing, updating.
It is sometimes necessary for the setup script to create dbus Settings
so GUI has access to them. This is often the case when the package
doesn't run its own service.

The following functions will create dbus settings if they do not already
exist or update their value if they

```bash
updateDbusStringSetting "/Settings/StringSetting" "the new string" 
updateDbusIntSetting "/Settings/IntegerSetting" 5
updateDbusRealSetting "/Settings/FloatingPointSetting" 6.0
```

**setSetting** is a function that updates the value of an existing dbus
Setting. It is faster than calling one of the above update... functions.
The new value can be any data type but strings must be quoted.

```bash
setSetting "/Settings/foo" "new string" 
setSetting "/Settings/bar" 18
```

The following function removes the settings. Limit the number of
settings to about 20 to avoid some being missed (not sure why). It is
faster to remove multiple settings at the same time than it is to call
'removeDbusSettings' for each one.

```bash
removeDbusSettings "/Settings/foo" "/Settings/bar" 
```

## SetupHelper variables

SetupHelper manages or tests a set of variables that control script
executions:

**$scriptAction** provides direction for the setup script and has the
following values:

- NONE - setup script should prompt the user for the desired action and
  set scriptAction accordingly

- EXIT - the setup script should exit immediately

- INSTALL - the setup script should execute code to install the package

- UNINSTALL - the setup script should execute the code to restore the
  Venus files to stock If installation errors occur within functions in
  CommonResources, scriptAction will be changed to UNINSTALL.

The setup script MUST retest scriptAction after all installation code
has been executed so the package can be removed, rather than leaving the
system with a partially installed package.

**$rebootNeeded** - true signifies a reboot is required after the
script is run. The setup script should set **rebootNeeded** to true if a
reboot is needed following install/uninstall

*The following variables contain useful information but should not be
changed by the setup script:*

- **$scriptDir** - the full path name to the startup script the
  script\'s code can use this to identify the location of files stored
  in the package

- **$scriptName** - the basename of the setup script ("setup")

- **$reinstallScriptsList** - the file containing a list of scripts to be
run at boot to reinstall packages after a Venus software update (by default, this is
/data/reinstallScriptsList)

- **$installedVersionFile** - the name of the installed version file

- **$venusVersion** - the version of VenusOS derived from
/opt/victronenergy/version

- **$fileList** - the version-dependent location for the replacement
files

- **$fileListVersionIndependent** - the location for files that are
independent of Venus OS version

- **$fileListPatched** - the location for files that are to be patched
prior to install

> [!NOTE] 
> Prior to SetupHelper v6.0 version-independent replacement files
were in $pkgFileSets directory.

- **$pkgFileSets** - is the location of all file sets

- **$fileSet** - is the location of version-dependent files for the
current Venus version

- **$runningAtBoot** - true if the script was called from reinstallMods (at boot time) 
 
  signifying this is to be an unattended (automatic) installation

  CommonResoures sets this variable based on command line options

- **$setupOptionsDir** - the location of any files that control installation

  These options are maintained in a separate directory so reinstalling
  the package does not remove them so that a reinstall can proceed
  without prompting again

- **$obsoleteVersion** - prevents installation starting with this Venus
OS version

- **$firstCompatibleVersion** - prevents installation *before* with this
Venus OS version

## Package lists

It is usually necessary to create a specific replacement file for
different Venus OS versions. This allows the package to be installed
regardless of the Venus version. These different replacement file
versions are contained in a "file set": a directory with the Venus OS
version number as it's name. The collection of file sets is stored in
the 'FileSets' directory in the package directory.

Some files in a package may not be tied to specific Venus OS versions.
These are typically additions to the stock files, and when a single file
can be used across all Venus OS versions. Prior to SetupHelper v6.0,
these *version-independent* files were contained in the FileSets
directory. Starting with v6.0, they are located in the
**VersionIndependent** file set.

These two file lists are kept separate because they are treated
differently.

The list **fileList** has always been used by helper resources to guide
installation of *version-dependent* files

Starting with v6.0, three additional lists have been added:

**fileListVersionIndependent** lists all *version-independent* files.
Those files exist in the **VersionIndependent** file set.

**fileListPatched** is a similar list for any replacement files that are
created with the unix patch command. Patch replacements are described
above.

The file lists consist of one line per file with the full path and name
of the file on each line.

The **DbusSettingsList** contains a list of dBus Settings to be added to
the system as part of this package. Settings are traditionally added via
a service but in cases where the package does not have a related
service, this mechanism allows there creation or update from the setup
script. Lines in **DbusSettingsList** are in the format:

```json
{"path":"/Settings/Relay/0/Show", "default":1, "min":0, "max":1}
```

"default" defines the default value plus the data type (1 for an
integer, 1.0 for float, "something" for a string)

"min" and 'max" are optional and set the range of acceptable values.

Refer to Victron dbus documentation for more details

File list for version-dependent files only:

```
/data/< package name >/FileSets/fileList
```

File list for version-independent files:

```
/data/< package name >/FileSets/fileListVersionIndependent
```

File list for patched files:

```
/data/\< package name \>/FileSets/fileListPatched
```

File list for dBus Settings:

```
/data/< package name >/DbusSettingsList
```

### Missing active file directories

Recently, Victron Energy has changed the name of some directories which contain files requring modificaiton by the package.
For example, /opt/victronenergy/bus-generator-starter was renamed /opt/victronenergy/dbus-generator.

In order to accommodate these name changes, SetupHelper v8.23 checks for the existance of the enclosing directory
for an active file and skips the update if the directory does not exist. This is logged but installation is allowed to continue.

[!NOTE]
Developers should review such log entries to insure there are no missing active file updates!

In such situations **fileList** and **fileListPatched** must include **both** enclosing directories. E.g.,:

```bash
/opt/victronenergy/dbus-generator-starter/startstop.py
/opt/victronenergy/dbus-generator/startstop.py
```

## Version file

A package must contain a version file. This is the *package* version,
not the Venus OS version. The package version is used by PackageManager
to decide if an automatic download is needed by comparing the version
from GitHub with the version stored on the system. Likewise, the stored
version is compared to the installed version to trigger an automatic
install.

The version file is a text file with a single line of the form: v1.2,
v1.2\~3 v1.2a3.

Versions that include a \~ or lettered version are treated as
pre-release.

- 'd' represents a development release

- 'a' represents an alpha release 
- 'b' or '\~' represents a beta release
- none of the above represents a released version

Version numbers are prioritized: 'a' is "newer" than 'd', etc.

"newer" versions will replace older versions when automatically
downloading. Exception: if the branch/ tag set in PackageManger is a
specific version (e.g., v.4.6) the stored version must match rather than
being older than. Installs always occur if the versions do not match.

## Restricted install

The package may optionally contain files that place restrictions on
which Venus OS versions or platforms the package may be installed on. If
any of these tests fail, the install will also fail!

If present **obsoleteVersion** identifies the first version that are not
compatible with this package. E.g., of obsoleteVersion contains v7.2 and
the current Venus OS version if v8.0, then the package can't be
installed.

If present **firstCompatibleVersion** identifies the first version that
IS compatible with this package. E.g., if firstCompatibleVersion
contains v8.0 and the current Venus OS version is v7.2, the package
can't be installed.
If **firstCompatibleVersion** is not present, SetupHelper uses v3.10 as the first compatible version.

Note that if both **firstCompatibleVersion** and **obsoleteVersion** are
included in the package directory, the obsoleteVersion must be greater
than firstCompatibleVersion.

If present **validFirmwareVersions** identifies all versions which have been
tested as compatible with the package. It is a list of Venus OS versions, one per line.
If this file is present, **firstCompatibleVersion** and **obsoleteVersion** are redundant.

If the file **raspberryPiOnly** exists in the package directory, the
platform (aka 'machine') MUST be raspberrypi2 or raspberrypi4. If not,
installation will be blocked.

Many packages modify the GUI file system. With the introduction of
gui-v2, some systems will not have the GUI v1 files in place or will not
be running the original GUI.

If GUI v1 files are required for the package, it's installation will
fail. In some cases, the GUI modifications are not essential for package
functionality, so if the flag file **GUI_V1_NOT_REQUIRED** is included
in the package's root directory the package install will not consider
missing GUI v1 files an error.

GUI v1 files are those found in /opt/victronenergy/gui. If files from
that directory appear in the file list and **GUI_V1_NOT_REQUIRED** is
not in the package directory, the install will not be permitted. A check
is also made in **updateActiveFile** and will force a package uninstall.

**NOTE: SetupHelper will allow an install if the GUI v1 files are
present on the system. However, GUI v1 may not currently be running in
which case, the user will not have access to the added/modified menus.**

Failed installs force an UNINSTALL.

## Package conflict management

Prior to SetupHelper v6.12, packages may interact with each other in
undesirable ways. For example, one package that modifies the same file
as another will install over the other package, removing the first
package's modifications. Uninstalling either package will result in
the stock file being used.

SetupHelper v6.12 adds logic to prevent this from happening. If a
package attempts to modify the same file as another package, the
install will fail and the package will be uninstalled. Beginning with
v8.0, multiple packages may be able to modify the same active file.
See the section on patching files above.

The **packageDependencies** file located in the package directory
defines basic requirements that would prevent the package from being
installed.

Each line of the file includes a package name and whether that package
must be installed or uninstalled in order for the package to be
installed. For example the file for RemoteGPIO might be:

```
RpiGpioSetup uninstalled
GuiMods installed
```

These lines tell SetupHelper to block the install of RemoteGPIO unless
RpiGpioSetup is uninstalled and unless GuiMods is installed.

Note that no changes to other package installations occurs so it would
be acceptable for the dependency file for RpiGpioSetup to also specify
that RemoteGPIO should be uninstalled. This way only one can be
installed but the user has that choice.

This mechanism is simple but has a drawback: **Package authors must
manually check for conflicts and coordinate with other package authors
on how best to address the conflict.**

## endScript

The **endScript** function must be called at the end of the setup
script. It determines the return code used by the caller (like
PackageManager) to provide the necessary user prompting and to control
reboot and service restarts.

**endScript** NEVER RETURNS to the caller !

The actions taken by endScript depend on a number of shell variables set
previously and on parameters passed when calling the function:

- The following parameters are passed from the caller. All optional:

  - **INSTALL_FILES** causes endScript to install/uninstall based on fileList, fileListVersionIndependent and fileListPatched lists.

  - **INSTALL_SERVICE** causes endScript to install/uninstall services located in the package services directory.
  
  - **ADD_DBUS_SETTINGS** causes endSctipt to perform the file addition or update of dBus Settings based on the DbusSettingsList in the package
  directory.

- If **$runningAtBoot** is true the script will exit with **EXIT_REBOOT** if **$rebootNeeded** is true otherwise, the script will exit with **EXIT_SUCCESS** on success.

- If **$runningAtBoot** is false (script was run manually), user
  interaction controls further action If **$rebootNeeded** is true, the
  function asks if the user wishes to reboot now. If they respond yes,
  the system will be rebooted. The user may choose to not reboot now if
  additional installations need to be done first

- If **$rebootNeeded** is false, the function notifies the user of any
 needed actions

- If **$restartGui** is true the gui service will be restarted

Starting with SetupHelper v6.0, other services will be restarted by
endSctipt if related files are changed with updateActiveFile or
restoreActiveFile. Refer to the updateRestartFlags function in
CommonResources for details.

If the setup script is run from the command line (no command line
options), **endScript** will prompt the user for a reboot or GUI restart
if one is needed. The user can choose to trigger the action now or wait
and do it manually later.

However, if the script is running autonomously, the action will be
triggered from within endScript, *unless* the script was run with
**deferReboot** or **deferGuiRestart** on the command line. In this
case, the action is not performed but the script exits with the
appropriate exit code. PackageManager and **renstallMods** use the exit
code to choose a course of action following all automatic operations.
For manual install/uninstall from PackageManger, the user is given the
choice to perform the GUI restart or reboot now or do it later. If
deferred, a message will be displayed indicating the package isn't fully
active ("reboot needed").

For reinstalls following a Venus OS update, **reinstallMods** will
reboot the system or restart the GUI after installing SetupHelper.

When the setup script completes an install operation, **endScript**
writes the package version to a file in / etc/venus. **endScript**
deletes this file during an uninstall. The installed version file
written to /etc/venus tells PackageManger which version of each package
is installed and running. It also tells the reinstall mechanism to skip
SetupHelper reinstall. So reinstall only happens if the installed
version file is NOT present or the installed version differs from the
package version itself. The latter may be the case if a Venus OS
firmware update has occurred.

Some packages may need to reboot in the middle of the installation
process. For example, if an overlay is needed to test for a specific
condition, the setup script should install the overly, but skip the
remaining setup, then set the **runAgain** shell variable before calling
endScript. endScript then removes the installed version file so the next
boot will run the package's setup script again.

If an install operation fails, it sets the shell variable
**installFailed**. **endScript** will then switch from INSTALL to
UNINSTALL to insure that all stock files are restored and the system is
not left in a partially modified state. **installFailed** is set by most
utility functions but should also be set inside any code in the setup
script that detects a failure. Also, any code should test
**installFailed** before proceeding with file modifications.

### endScript exit codes

The following is a list of exit codes returned when endScript exits:

- EXIT_SUCCESS=0 no further action needed

- EXIT_REBOOT=123 system reboot needed

- EXIT_RESTART_GUI=124 GUI restart needed

- EXIT_INCOMPATIBLE_VERSION=254 install failed - version not compatible

- EXIT_INCOMPATIBLE_PLATFORM=253 install failed - platform not compatible

- EXIT_FILE_SET_ERROR=252 install failed - file set problems

- EXIT_OPTIONS_NOT_SET=251 install failed

  - run setup script from command line

- EXIT_RUN_AGAIN=250 partial install

  - run script again after reboot

- EXIT_ROOT_FULL=249 install failed - no room on root

- EXIT_DATA_FULL=248 install failed - no room on /data

- EXIT_NO_GUI_V1=247 install failed - GUI V1 needed

- EXIT_PACKAGE_CONFLICT=246 install of this package blocked by another package

- EXIT_ERROR=255 install failed - unknown error

## PackageManager

Package Manager includes a set of menus on the GX device menu system
that allows the user to view package versions, control automatic package
updates and manually install, uninstall, add and remove packages. This
provides an alternative to the command line interface for package
management.

A PackageManager is a python program that runs as a service to do the
actual work and to interface with the menus.

### Package Manager menu

The first line of this menu provides status for Package Manager,
indicating what it is currently doing

**Automatic GitHub downloads** controls if packages are automatically
downloaded from GitHub. This occurs if a newer version is available.

- **On** checks GitHub for a package that is newer than what is stored on
the system

- **Once** checks GitHub for a package, then downloads are turned off

- **Off** disables GitHub downloads

GitHub versions are refreshed every 10 minutes if auto downloads is
turned on. If auto downloads are off GitHub versions are refreshed once
when entering the Package Versions menu. A specific package's GitHub
version is also refreshed when entering the Package edit menu.

If auto downloads are off, GitHub versions expire after 10 minutes.

**Auto install packages** controls whether new versions of a package are
automatically installed. Some users may wish to have the system
automatically download new updates, but install them manually. In this
case, automatic GitHub downloads may be turned on and Auto install
packages turned off.

Auto install packages also influences whether packages transferred from
SD/USB media are automatically installed or just transferred to local
storage

**Active packages** and **Inactive packages** lead to menus described
below

**action to finish install/uninstall** appears of a system reboot or GUI
restart has been deferred (see the Package editor menu)

**Backup & restore settings** leads to the menu described later

**microSD / USB** indicates if removable media has been detected and
allows it to be ejected prior to removal.

**Restart or initialize ...** leads to the menu described below

### Active packages menu

Displays all active packages, and allows access to editing the package
setup

Tapping on one of the entries leads to the Package editor menu

Version information is displayed for each package:

- GitHub shows the version found on GitHub

- Stored shows the version stored on the GX device

- Installed shows the version actually installed and running

> [!NOTE] 
> If the GitHub version is not shown, check the GitHub user and
branch/tag, or check your internet connection.

### Package editor menu

This menu facilitates manual install, uninstall, package removal as well
as changing GitHub access information for the package.

**GitHub user** is the name of the GitHub user authoring the package.
Normally this won't change.

**GitHub branch or tag** allows you to specify a branch or specific tag.
The default (typically **latest**) references the latest released
version of the package. You can change this field to try out a beta
version or revert to a specific version. Once the GitHub branch is
changed, PackageManager will update the GitHub version. If auto
downloads are active the new version will be downloaded automatically.

The status line shows progress of pending operations, conflicts, or
prompts for further actions.

**Previous** and **Next** step through other packages without leaving
this menu.

The remaining buttons along the bottom of the menu allow for
**Download**, **Install**, **Uninstall** or **Remove**. These operations
require a confirmation via **Proceed** or **Cancel** in the status line.

**Remove** will remove the package from the active package list and
return it to the inactive packages list. Packages that are of no
interest can be removed to keep the active list cleaner.

Package manager does not allow removing packages unless they are
uninstalled first.

Package manager DOES permit uninstalling SetupHelper, however this will
remove the Package Manager itself. Once removed, the Blind Install
mechanism will be needed again !!

**Show Conflicts** appears in the status line if package conflicts
exist. Pressing that shows a list of conflicts and if possible asks if
they should be resolved. If they are, **Proceed** and **Cancel** appear.
Pressing **Proceed** will trigger the necessary package installs and
uninstalls needed to resolve the conflicts

If an operation requires a system reboot or GUI restart, a message
appears in the status line. **Now** triggers that operation. **Later**
hides the notification without acting on it. This can be handy if you
are performing multiple operations. The notification will appear when
navigating to other packages.

### Inactive packages menu

Displays all INACTIVE packages, i.e., default packages not yet activated
or manually remove.

The first entry is always \"new\" and allows the operator to enter
package name, GitHub user and branch/tag from scratch

Additional lines (if any) are default packages (from the
defaultPackageList file)

If a package is already added to the version list, it will not appear in
the Add Package list

Tapping on one of the entries leads to the Add package menu

### Add package menu

Allows the package name, **GitHub user** and **GitHub branch or tag** to
be entered or changed and the package added to the active packages list.
These are the same as described above under Package editor menu.

Prompting for required information is provided on the status line.

Pressing **Proceed** initiates the package add. **Cancel** returns to
the Inactive Packages menu.

The package name must be unique or the add operation will fail with a
prompt indicating to choose a different name.

### Backup & Restore settings menu

Saves settings to the settingsBackup file on removable SD/USB media or
to local media (`/data/settingsBackup`). restores from same.

`/data/SetupHelper/settingsList` is a complete list of settings saved to
settingsBackup. Categories are:

- GuiMods
- SetupHelper / PackageManager
- ShutdownManager
- SOME Victron stock settings in the following sections
 
  - Alarms
  - Gwacs
  - DigitalInputs

  - Generators
  - Gui
  - Pump
  - Relay
  - System
  - SystemSetup
  - Vrmlogger

Additionally, backup and restore the following to/from removable media


- Any logo files in `/data/themes/overlay` 
- Setup script options in `/data/setupOptions`

All logs stored in `/data/log` are written to logs.zip on removable media
as part of a backup operation

The parameters must exist to be saved. The parameters will be created
and set to the backed up value during a restore.

> [!NOTE]
> Victron is working on a more comprehensive mechanism but is not
> working reliably yet. The Package manager backup and restore is
> temporary and will be removed when the Victron functionality is
> working

### Package manager restart/initialize menu

This menu provides a quick way to reboot the system (**Restart**),
restart the GUI (**Restart GUI**) or initialize Package manager.
(**Initialize**). The latter can be used to clean up Package manager's
persistent storage. Any custom packages added manually or any GitHub
user or branch/tag information will be lost.

### USB/SD updates

When the GX device is not connected to the internet, a USB flash drive
or microSD card provides an install/upgrade path. To use the USB update
process

1.  Navigate to the GitHub, repo, click on tags and select the
    appropriate branch or specific version.

2.  Choose the .tar.gz download link.
(Do not download from the Code tab
    or download the .zip file. These won\'t work.)

3.  Copy the archive file to a USB memory stick or microSD card.
Do NOT
    unpack the archive

4.  Repeat this for all packages you wish to install. (These can all be
    placed on the same media along with the SetupHelper blind install
    `venus-data.tgz` file)

5.  Insert the stick in the GX device.

6.  If SetupHelper has not yet been installed, follow the Blind Install
    process from the ReadMe.

Once Package Manager is running, it will transfer and unpack the archive
files and update the package list with the new packages.

If Auto install packages is turned on, the packages will then be
installed

> [!NOTE]
> No version checks are made for packages found on SD/USB media!
> Package Manager is quite content to transfer and install an older
> version so make sure you have the latest version especially if your GX
> device does not have internet access.

### Package manager control via removable media

Besides the menus described above, Package manager can be controlled via
"flag" files on removable media. These flag files trigger behavior if
they are detected. The file contents is not important, only the
existence of the file.

**SETTINGS_AUTO_RESTORE**

An automatic settings restore will be performed when PackageManager if
the file is present.

> [!CAUTION]
> Leaving this removable media in the system will trigger
> settings restore with every boot. You must remove the flash drive
> after auto restore

**AUTO_EJECT**

ALL removable media is ejected after the media is scanned AND if after
all transferrers were performed.

Removable media can be corrupted if removed while the VRM logger is
still writing to it so the drive must be ejected to prevent corruption.
A manual eject button is included in the PackageManager menu.

Unfortunately, the eject mechanism ejects all removable media, not just
a specific one. The VRM logger automatically uses the first removable
media found so there is no control over it, and the presence of
AUTO_EJECT will eject the media for the logger also.

**AUTO_INSTALL_PACKAGES**

All packages will be installed even if the Auto Install menu option is
turned off. This is generally used only for system deployment (see
below).

**AUTO_UNINSTALL_PACKAGES**

As above, but will uninstall all packages found in /data. This is useful
if you do not have command line access and end up with a GUI that is
unresponsive or just to clean up a system, returning it (almost) to
factory defaults. This flag file overrides AUTO_INSTALL_PACKAGES if both
are present

The system is rebooted after the uninstall all just to be sure there\'s
nothing left behind.

**AUTO_INSTALL**

If the file AUTO_INSTALL is present in a **package directory**, the
package will be installed as if the auto install option is set in the
PackageManager menu. Version checks are still performed and
DO_NOT_INSTALL is honored.

**ONE_TIME_INSTALL**

If the file ONE_TIME_INSTALL is present in a **package directory**, the
package is automatically installed even if automatic installs are
disabled and the DO_NOT_INSTALL flag is set

ONE_TIME_INSTALL is removed when the install is performed to prevent
repeated installs. Packages may be deployed with this flag set to insure
it is installed when a new version is transferred from removable media
or downloaded from GitHub

**INITIALZE_PACKAGE_MANAGER**

The PackageManager's persistent storage is rebuilt (see Initialize
above)

## updatePackage

**updatePackage** is a unix shell script that runs on the development
computer, not the GX device. It is included in the SetupHelper package.
The comments at the top of the script provide additional details.

Windows will not run this script natively. However Windows 10 apparently
supports bash:

https://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-on-windows-10/

Windows developer options must be enabled. In addition, differences in
end of line between platforms may need to be manually adjusted (cr-lf
for Windows) for the script to run properly.

Another option is to run **updatePackage** on a unix virtual machine or
a Raspberry PI running it's native OS.

In order to identify changed files, the original file for each
replacement must be compared against ALL versions of Venus OS. When
changes are detected, a new file set version directory needs to be
created and a new \...orig file copied from the stock Venus OS file.
This work is done on the computer creating the package, not on the GX
device.

I use the raspberry Pi images from
http://updates.victronenergy.com/feeds/venus/ because these contain the
compete file system. Alternatively, the file system can be copied from a
running GX device ***prior*** to installing any packages. I create a
directory on the managing computer called OriginalFiles, then create a
directory for each Venus OS version: v2.81, v2.90\~12, etc.

Next, I copy the /etc, /opt and /var/www/venus/styling directories from
the Venus OS image to the OriginalFiles/vX.Y\~Z directory.

I limit StockFiles to these directories to minimize storage space on the
system used to generate the package files.

99% of the files likely to be modified by a package are located there.

This is an artificial limit and other parts of the file system may be
included if needed. In order to run the necessary checks, Venus OS
versions need to be available to the managing computer.

Starting with SetupHelper v5.0, it is recommended that a file set exist
for all supported Venus OS versions. This speeds installation time
because it is not necessary to build a file set for a missing Venus OS
version. CommonResources will attempt to create a missing file set,
however it may not be possible to create one if version-dependent
original files don't match a file for other file sets.

**updatePackage** runs through all StockFiles version directories and
all package directories and creates file sets in each package.

Before running this script, you need to edit the FileSets/fileList\*
files to include the files your package will modify. Use full path names
to avoid issues.

File sets created with **updatePackage** will contain ALL replacement
files (or symbolic links to an identical replacement in another file
set) in every file set. This change prevents a problem where a matching
original file could not be found even though a file set does exist for
the current Venus OS version. This resulted in an "incomplete file set"
error and failure to install the package. To clear the error, it was
necessary to reinstall the package and/or the Venus OS firmware. Prior
to v5.0, these symbolic links were missing, and in fact entire file sets
might be missing if they can be created from other Venus OS versions. It
was then necessary for **\_checkFileSets** to fill in the missing files
for the current Venus OS version.

Starting with SetupHelper v6.0, **updatePackage** will relocate all
version-independent files included in the fileListVersionIndependent
list. Prior to v6.0, these files were located in the FileSets directory.
Starting with v6.0, these files are located in the VersionIndependent
file set

**updatePackage** provides an option to relocate alternate original
files (described below) to their new locations: AlternateOriginals. If
AlternateOriginals is already present, the move will be automatic.

**checkFileSets** in CommonResources checks for a COMPLETE flag file
before attempting to fill in a missing file set. This saves time since a
search is not needed for all replacement files. (In previous versions of
SetupHelper, a test was made for all replacement files. This should
succeed but takes time.)

File sets created with **updatePackage** from an older version of
SetupHelper only contain replacement files when the original file
changes between versions. Older file sets will also not contain the
COMPLETE flag file. Also, the package may not contain a file set for the
current Venus OS version. **checkFileSets** will attempt to fill in
missing files or build a missing file set by comparing the original
files in other file sets with the active file installed by Venus OS. If
a match is found, this means the replacement file from the other file
set also applies to the current Venus OS version.

After running this script, you may find file sets populated with
`...NO_REPLACEMENT` files. These indicate where you need to create
replacement files for your packages. You will need to add your changes
to each replacement file in each file set.

Naming:

- the replacement file has the extension of the actual file, e.g.,
PageMain.qml

- the original file adds a .orig extension, e.g, PageMain.qml.orig

- if no original exists, an empty file with the `.NO_ORIG` file will be
created. e.g., `PageMain.qml.NO_ORIG`

Existence of a `.NO_ORG` file after running updateFileSets indicates a
significant problem. What this says is that the replacement file has no
equivalent in a stock system. If the replacement file is the same for
all Venus OS versions, simply remove it from fileList and place the
replacement in the FileSets directory, not in a version directory.

However, if the replacement file differs between Venus OS versions, an
alternate original file needs to be used as a reference. For example, if
you are creating a new file `PageMainEnhanced.qml`, then you can probably
use `PageMain.qml` as the alternate original. Create a file in FileSets
named `PageMainEnhanced.qml.ALT_ORIG` with a single line with the full
path to the alternate original:

`/opt/ victronenergy/qui/qml/PageMain.qml`

Sometimes, a replacement file is needed in SOME versions of Venus OS but
in others. An empty file in the file set will instruct SetupHelper to
use the orig, e.g., `PageMain.qml.USE_ORIGINAL`

**Starting with SetupHelper v6.0**, alternate original files can optionally
be stored in the alternateOriginals directory in the FileSets directory.
This removes clutter from the FileSets directory but the functionality
is the same.

## Blind Install

By far, the easiest way to install SetupHelper is the \"blind install"
which requires no command-line interaction.

1. Download [venus-data.tgz](https://github.com/kwindrem/SetupHelper/raw/main/venus-data.tgz) from the SetupHelper GitHub repo.

    > **Note:** Mac OS and Safari are set by default to unzip packages. The Open
    > "safe" files after downloading (bottom of Safari Preferences
    > General) must be disabled in order to retain the zip file.

2.  copy it to the root of a freshly formatted SD card or USB memory
    stick

3.  place the media in the GX device (Cerbo, CCGX, etc)

4.  reboot the GX device and allow the system to display the GUI

    > If you are running Venus OS v2.90 and beyond you should find the
    > Package Manager menu at the bottom of the Settings menu

5.  you should remove the media at this point. Mechanisms are in place
    to prevent reinstallation, but removal is still a good idea

If you a
Download .txt
gitextract_d5l_5hwy/

├── .github/
│   └── workflows/
│       └── latest-tag.yml
├── FileSets/
│   ├── PatchSource/
│   │   ├── PageSettings.qml
│   │   ├── PageSettings.qml.orig
│   │   └── PageSettings.qml.patch
│   ├── VersionIndependent/
│   │   ├── MbDisplayDefaultPackage.qml
│   │   ├── MbDisplayPackageVersion.qml
│   │   ├── PageSettingsAddPackageList.qml
│   │   ├── PageSettingsPackageAdd.qml
│   │   ├── PageSettingsPackageEdit.qml
│   │   ├── PageSettingsPackageManager.qml
│   │   ├── PageSettingsPackageVersions.qml
│   │   ├── PageSettingsPmBackup.qml
│   │   └── PageSettingsPmInitialize.qml
│   ├── fileListPatched
│   └── fileListVersionIndependent
├── HelperResources/
│   ├── CommonResources
│   ├── DbusSettingsResources
│   ├── EssentialResources
│   ├── IncludeHelpers
│   ├── LogHandler
│   ├── ServiceResources
│   └── VersionResources
├── PackageDevelopmentGuidelines.md
├── PackageManager.py
├── ReadMe.md
├── blindInstall/
│   ├── SetupHelperVersion
│   ├── blindInstall.sh
│   ├── post-hook.sh
│   ├── pre-hook.sh
│   └── rcS.localForUninstall
├── changes
├── defaultPackageList
├── forSetupScript
├── genericSetupScript
├── gitHubInfo
├── makeVelib_python
├── patch
├── patchBookworm
├── rcS.local
├── reinstallMods
├── services/
│   └── PackageManager/
│       ├── log/
│       │   └── run
│       └── run
├── settingsList
├── setup
├── updatePackage
├── velib_python/
│   ├── dbusmonitor.py
│   ├── oldestVersion
│   ├── settingsdevice.py
│   ├── ve_utils.py
│   ├── vedbus.py
│   └── velib_python/
│       ├── latest/
│       │   ├── dbusmonitor.py
│       │   ├── oldestVersion
│       │   ├── settingsdevice.py
│       │   ├── ve_utils.py
│       │   └── vedbus.py
│       ├── v3.34/
│       │   ├── dbusmonitor.py
│       │   ├── oldestVersion
│       │   ├── settingsdevice.py
│       │   ├── ve_utils.py
│       │   └── vedbus.py
│       └── v3.41/
│           ├── dbusmonitor.py
│           ├── oldestVersion
│           ├── settingsdevice.py
│           ├── ve_utils.py
│           └── vedbus.py
├── venus-data-SetupHelperInstall.tgz
├── venus-data-UninstallAllPackages.tgz
└── version
Download .txt
SYMBOL INDEX (607 symbols across 17 files)

FILE: PackageManager.py
  function VersionToNumber (line 419) | def VersionToNumber (version):
  function PushAction (line 512) | def PushAction (command=None, source=None):
  function LocatePackagePath (line 661) | def LocatePackagePath (origPath):
  class AddRemoveClass (line 694) | class AddRemoveClass (threading.Thread):
    method __init__ (line 696) | def __init__(self):
    method StopThread (line 719) | def StopThread (self):
    method run (line 726) | def run (self):
  class DbusIfClass (line 883) | class DbusIfClass:
    method RemoveDbusSettings (line 893) | def RemoveDbusSettings (cls, settingsList):
    method UpdateStatus (line 940) | def UpdateStatus ( self, message=None, where=None, logLevel=0 ):
    method UpdatePackageCount (line 952) | def UpdatePackageCount (self):
    method GetPackageCount (line 955) | def GetPackageCount (self):
    method SetAutoDownloadMode (line 957) | def SetAutoDownloadMode (self, value):
    method GetAutoDownloadMode (line 959) | def GetAutoDownloadMode (self):
    method GetAutoInstall (line 961) | def GetAutoInstall (self):
    method SetAutoInstall (line 963) | def SetAutoInstall (self, value):
    method SetPmStatus (line 969) | def SetPmStatus (self, value):
    method SetMediaStatus (line 971) | def SetMediaStatus (self, value):
    method SetDefaultCount (line 974) | def SetDefaultCount (self, value):
    method GetDefaultCount (line 976) | def GetDefaultCount (self):
    method SetBackupMediaAvailable (line 979) | def SetBackupMediaAvailable (self, value):
    method GetBackupMediaAvailable (line 985) | def GetBackupMediaAvailable (self):
    method SetBackupSettingsFileExist (line 991) | def SetBackupSettingsFileExist (self, value):
    method SetBackupSettingsLocalFileExist (line 998) | def SetBackupSettingsLocalFileExist (self, value):
    method GetBackupSettingsFileExist (line 1005) | def GetBackupSettingsFileExist (self):
    method SetBackupProgress (line 1011) | def SetBackupProgress (self, value):
    method GetBackupProgress (line 1013) | def GetBackupProgress (self):
    method AcknowledgeGuiEditAction (line 1027) | def AcknowledgeGuiEditAction (self, value, defer=False):
    method SetEditStatus (line 1037) | def SetEditStatus (self, message):
    method handleGuiEditAction (line 1054) | def handleGuiEditAction (self, path, command):
    method LocateRawDefaultPackage (line 1078) | def LocateRawDefaultPackage (self, packageName):
    method UpdateDefaultPackages (line 1091) | def UpdateDefaultPackages (self):
    method ReadDefaultPackagelist (line 1140) | def ReadDefaultPackagelist (self):
    method LOCK (line 1166) | def LOCK (self, name):
    method UNLOCK (line 1186) | def UNLOCK (self, name):
    method __init__ (line 1193) | def __init__(self):
    method RemoveDbusService (line 1254) | def RemoveDbusService (self):
  class PackageClass (line 1294) | class PackageClass:
    method LocatePackage (line 1308) | def LocatePackage (cls, packageName):
    method GetAutoAddOk (line 1323) | def GetAutoAddOk (cls, packageName):
    method SetAutoAddOk (line 1336) | def SetAutoAddOk (cls, packageName, state):
    method SetAutoInstallOk (line 1358) | def SetAutoInstallOk (self, state):
    method SetPackageName (line 1380) | def SetPackageName (self, newName):
    method SetInstalledVersion (line 1384) | def SetInstalledVersion (self, version):
    method SetPackageVersion (line 1391) | def SetPackageVersion (self, version):
    method SetGitHubVersion (line 1398) | def SetGitHubVersion (self, version):
    method SetGitHubUser (line 1405) | def SetGitHubUser (self, user):
    method SetGitHubBranch (line 1409) | def SetGitHubBranch (self, branch):
    method SetIncompatible (line 1413) | def SetIncompatible (self, value, details="", resolvable=False):
    method settingChangedHandler (line 1427) | def settingChangedHandler (self, name, old, new):
    method __init__ (line 1440) | def __init__( self, section, packageName = None ):
    method AddPackagesFromDbus (line 1555) | def AddPackagesFromDbus (cls):
    method PackageNameValid (line 1584) | def PackageNameValid (cls, packageName):
    method AddStoredPackages (line 1619) | def AddStoredPackages (cls):
    method AddPackage (line 1667) | def AddPackage ( cls, packageName=None, gitHubUser=None, gitHubBranch=...
    method RemovePackage (line 1744) | def RemovePackage (cls, packageName=None, packageIndex=None, isDuplica...
    method UpdateVersionsAndFlags (line 1886) | def UpdateVersionsAndFlags (self, doConflictChecks=False, doScriptPreC...
  class UpdateGitHubVersionClass (line 2190) | class UpdateGitHubVersionClass (threading.Thread):
    method updateGitHubVersion (line 2213) | def updateGitHubVersion (self, packageName, gitHubUser, gitHubBranch):
    method __init__ (line 2242) | def __init__(self):
    method SetPriorityGitHubVersion (line 2255) | def SetPriorityGitHubVersion (self, command):
    method StopThread (line 2290) | def StopThread (self):
    method run (line 2295) | def run (self):
  class DownloadGitHubPackagesClass (line 2450) | class DownloadGitHubPackagesClass (threading.Thread):
    method __init__ (line 2452) | def __init__(self):
    method GitHubDownload (line 2466) | def GitHubDownload (self, packageName=None, source=None):
    method DownloadVersionCheck (line 2611) | def DownloadVersionCheck (self, package):
    method StopThread (line 2642) | def StopThread (self):
    method run (line 2656) | def run (self):
  class InstallPackagesClass (line 2717) | class InstallPackagesClass (threading.Thread):
    method __init__ (line 2719) | def __init__(self):
    method InstallPackage (line 2744) | def InstallPackage ( self, packageName=None, source=None , action='ins...
    method ResolveConflicts (line 2914) | def ResolveConflicts ( self, packageName=None, source=None ):
    method StopThread (line 2990) | def StopThread (self):
    method run (line 2994) | def run (self):
  class MediaScanClass (line 3072) | class MediaScanClass (threading.Thread):
    method transferPackage (line 3082) | def transferPackage (self, path, autoInstallOverride=False):
    method __init__ (line 3177) | def __init__(self):
    method settingsBackup (line 3196) | def settingsBackup (self, backupPath, settingsOnly = False):
    method settingsRestore (line 3311) | def settingsRestore (self, backupPath, settingsOnly = False):
    method StopThread (line 3424) | def StopThread (self):
    method run (line 3428) | def run (self):
  function mainLoop (line 3716) | def mainLoop ():
  function directUninstall (line 3958) | def	directUninstall (packageName ):
  function setPmRestart (line 3997) | def setPmRestart (signal, frame):
  function shutdownPmRestart (line 4001) | def shutdownPmRestart (signal, frame):
  function main (line 4018) | def main():

FILE: velib_python/dbusmonitor.py
  class SystemBus (line 37) | class SystemBus(dbus.bus.BusConnection):
    method __new__ (line 38) | def __new__(cls):
  class SessionBus (line 41) | class SessionBus(dbus.bus.BusConnection):
    method __new__ (line 42) | def __new__(cls):
  class MonitoredValue (line 45) | class MonitoredValue(object):
    method __init__ (line 46) | def __init__(self, value, text, options):
    method __iter__ (line 53) | def __iter__(self):
  class Service (line 56) | class Service(object):
    method __init__ (line 57) | def __init__(self, id, serviceName, deviceInstance):
    method __setitem__ (line 67) | def __setitem__(self, key, value):
    method __getitem__ (line 69) | def __getitem__(self, key):
    method set_seen (line 72) | def set_seen(self, path):
    method seen (line 75) | def seen(self, path):
    method service_class (line 79) | def service_class(self):
  class DbusMonitor (line 82) | class DbusMonitor(object):
    method __init__ (line 84) | def __init__(self, dbusTree, valueChangedCallback=None, deviceAddedCal...
    method make_service (line 139) | def make_service(serviceId, serviceName, deviceInstance):
    method make_monitor (line 143) | def make_monitor(self, service, path, value, text, options):
    method dbus_name_owner_changed (line 147) | def dbus_name_owner_changed(self, name, oldowner, newowner):
    method _process_name_owner_changed (line 154) | def _process_name_owner_changed(self, name, oldowner, newowner):
    method scan_dbus_service (line 174) | def scan_dbus_service(self, serviceName):
    method scan_dbus_service_inner (line 189) | def scan_dbus_service_inner(self, serviceName):
    method scan_dbus_service_getitems_done (line 286) | def scan_dbus_service_getitems_done(self, serviceName, serviceId, valu...
    method handler_item_changes (line 320) | def handler_item_changes(self, items, senderId):
    method handler_value_changes (line 342) | def handler_value_changes(self, changes, path, senderId):
    method _handler_value_changes (line 361) | def _handler_value_changes(self, service, path, value, text):
    method _execute_value_changes (line 382) | def _execute_value_changes(self, serviceName, objectPath, changes, opt...
    method get_value (line 397) | def get_value(self, serviceName, objectPath, default_value=None):
    method exists (line 410) | def exists(self, serviceName, objectPath):
    method seen (line 424) | def seen(self, serviceName, objectPath):
    method set_value (line 433) | def set_value(self, serviceName, objectPath, value):
    method set_value_async (line 449) | def set_value_async(self, serviceName, objectPath, value,
    method get_service_list (line 468) | def get_service_list(self, classfilter=None):
    method get_device_instance (line 479) | def get_device_instance(self, serviceName):
    method track_value (line 482) | def track_value(self, serviceName, objectPath, callback, *args, **kwar...
  function value_changed_on_dbus (line 520) | def value_changed_on_dbus(dbusServiceName, dbusPath, dict, changes, devi...
  function nameownerchange (line 530) | def nameownerchange(a, b):
  function print_values (line 540) | def print_values(dbusmonitor):
  function main (line 551) | def main():

FILE: velib_python/settingsdevice.py
  class SettingsDevice (line 28) | class SettingsDevice(object):
    method __init__ (line 38) | def __init__(self, bus, supportedSettings, eventCallback, name='com.vi...
    method addSettings (line 61) | def addSettings(self, settings):
    method addSetting (line 69) | def addSetting(self, path, value, _min, _max, silent=False, callback=N...
    method handleChangedSetting (line 98) | def handleChangedSetting(self, setting, servicename, path, changes):
    method setDefault (line 107) | def setDefault(self, path):
    method __getitem__ (line 111) | def __getitem__(self, setting):
    method __setitem__ (line 114) | def __setitem__(self, setting, newvalue):

FILE: velib_python/ve_utils.py
  class NoVrmPortalIdError (line 14) | class NoVrmPortalIdError(Exception):
  function exit_on_error (line 22) | def exit_on_error(func, *args, **kwargs):
  function get_vrm_portal_id (line 38) | def get_vrm_portal_id():
  function convert_vreg_version_to_readable (line 91) | def convert_vreg_version_to_readable(version):
  function get_free_space (line 124) | def get_free_space(path):
  function _get_sysfs_machine_name (line 136) | def _get_sysfs_machine_name():
  function get_machine_name (line 147) | def get_machine_name():
  function get_product_id (line 169) | def get_product_id():
  function read_file (line 192) | def read_file(path):
  function wrap_dbus_value (line 204) | def wrap_dbus_value(value):
  function unwrap_dbus_value (line 238) | def unwrap_dbus_value(val):
  function add_name_owner_changed_receiver (line 266) | def add_name_owner_changed_receiver(dbus, name_owner_changed, namespace=...

FILE: velib_python/vedbus.py
  class VeDbusService (line 60) | class VeDbusService(object):
    method __init__ (line 61) | def __init__(self, servicename, bus=None, register=True):
    method register (line 85) | def register(self):
    method __del__ (line 92) | def __del__(self):
    method get_name (line 103) | def get_name(self):
    method add_path (line 109) | def add_path(self, path, value, description="", writeable=False,
    method add_mandatory_paths (line 129) | def add_mandatory_paths(self, processname, processversion, connection,
    method _value_changed (line 145) | def _value_changed(self, path, newvalue):
    method _item_deleted (line 151) | def _item_deleted(self, path):
    method __getitem__ (line 162) | def __getitem__(self, path):
    method __setitem__ (line 165) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 168) | def __delitem__(self, path):
    method __contains__ (line 172) | def __contains__(self, path):
    method __enter__ (line 175) | def __enter__(self):
    method __exit__ (line 180) | def __exit__(self, *exc):
  class ServiceContext (line 186) | class ServiceContext(object):
    method __init__ (line 187) | def __init__(self, parent):
    method __contains__ (line 191) | def __contains__(self, path):
    method __getitem__ (line 194) | def __getitem__(self, path):
    method __setitem__ (line 197) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 202) | def __delitem__(self, path):
    method flush (line 207) | def flush(self):
    method add_path (line 212) | def add_path(self, path, value, *args, **kwargs):
    method del_tree (line 219) | def del_tree(self, root):
    method get_name (line 226) | def get_name(self):
  class TrackerDict (line 229) | class TrackerDict(defaultdict):
    method __missing__ (line 231) | def __missing__(self, key):
  class VeDbusRootTracker (line 235) | class VeDbusRootTracker(object):
    method __init__ (line 240) | def __init__(self, bus, serviceName):
    method __del__ (line 246) | def __del__(self):
    method add (line 250) | def add(self, i):
    method _items_changed_handler (line 253) | def _items_changed_handler(self, items):
  class VeDbusItemImport (line 293) | class VeDbusItemImport(object):
    method __new__ (line 294) | def __new__(cls, bus, serviceName, path, eventCallback=None, createsig...
    method __init__ (line 312) | def __init__(self, bus, serviceName, path, eventCallback=None, creates...
    method __del__ (line 338) | def __del__(self):
    method _refreshcachedvalue (line 344) | def _refreshcachedvalue(self):
    method path (line 349) | def path(self):
    method serviceName (line 354) | def serviceName(self):
    method get_value (line 361) | def get_value(self):
    method set_value (line 365) | def set_value(self, newvalue):
    method set_default (line 375) | def set_default(self):
    method get_text (line 385) | def get_text(self):
    method exists (line 390) | def exists(self):
    method eventCallback (line 404) | def eventCallback(self):
    method eventCallback (line 408) | def eventCallback(self, eventCallback):
    method _properties_changed_handler (line 413) | def _properties_changed_handler(self, changes):
  class VeDbusTreeExport (line 427) | class VeDbusTreeExport(dbus.service.Object):
    method __init__ (line 428) | def __init__(self, bus, objectPath, service):
    method __del__ (line 433) | def __del__(self):
    method _get_path (line 442) | def _get_path(self):
    method _get_value_handler (line 447) | def _get_value_handler(self, path, get_text=False):
    method GetValue (line 461) | def GetValue(self):
    method GetText (line 466) | def GetText(self):
    method local_get_value (line 469) | def local_get_value(self):
  class VeDbusRootExport (line 472) | class VeDbusRootExport(VeDbusTreeExport):
    method ItemsChanged (line 474) | def ItemsChanged(self, changes):
    method GetItems (line 478) | def GetItems(self):
  class VeDbusItemExport (line 487) | class VeDbusItemExport(dbus.service.Object):
    method __init__ (line 500) | def __init__(self, bus, objectPath, value=None, description=None, writ...
    method __del__ (line 513) | def __del__(self):
    method _get_path (line 524) | def _get_path(self):
    method local_set_value (line 533) | def local_set_value(self, newvalue):
    method _local_set_value (line 538) | def _local_set_value(self, newvalue):
    method local_get_value (line 548) | def local_get_value(self):
    method SetValue (line 559) | def SetValue(self, newvalue):
    method GetDescription (line 594) | def GetDescription(self, language, length):
    method GetValue (line 601) | def GetValue(self):
    method GetText (line 608) | def GetText(self):
    method PropertiesChanged (line 630) | def PropertiesChanged(self, changes):
  class weak_functor (line 636) | class weak_functor:
    method __init__ (line 637) | def __init__(self, f):
    method __call__ (line 641) | def __call__(self, *args, **kargs):

FILE: velib_python/velib_python/latest/dbusmonitor.py
  class SystemBus (line 42) | class SystemBus(dbus.bus.BusConnection):
    method __new__ (line 43) | def __new__(cls):
  class SessionBus (line 46) | class SessionBus(dbus.bus.BusConnection):
    method __new__ (line 47) | def __new__(cls):
  class MonitoredValue (line 50) | class MonitoredValue(object):
    method __init__ (line 51) | def __init__(self, value, text, options):
    method __iter__ (line 58) | def __iter__(self):
  class Service (line 61) | class Service(object):
    method __init__ (line 62) | def __init__(self, id, serviceName, deviceInstance):
    method __setitem__ (line 72) | def __setitem__(self, key, value):
    method __getitem__ (line 74) | def __getitem__(self, key):
    method set_seen (line 77) | def set_seen(self, path):
    method seen (line 80) | def seen(self, path):
    method service_class (line 84) | def service_class(self):
  class DbusMonitor (line 87) | class DbusMonitor(object):
    method __init__ (line 89) | def __init__(self, dbusTree, valueChangedCallback=None, deviceAddedCal...
    method make_service (line 144) | def make_service(serviceId, serviceName, deviceInstance):
    method make_monitor (line 148) | def make_monitor(self, service, path, value, text, options):
    method dbus_name_owner_changed (line 152) | def dbus_name_owner_changed(self, name, oldowner, newowner):
    method _process_name_owner_changed (line 159) | def _process_name_owner_changed(self, name, oldowner, newowner):
    method scan_dbus_service (line 179) | def scan_dbus_service(self, serviceName):
    method scan_dbus_service_inner (line 194) | def scan_dbus_service_inner(self, serviceName):
    method scan_dbus_service_getitems_done (line 291) | def scan_dbus_service_getitems_done(self, serviceName, serviceId, valu...
    method handler_item_changes (line 325) | def handler_item_changes(self, items, senderId):
    method handler_value_changes (line 347) | def handler_value_changes(self, changes, path, senderId):
    method _handler_value_changes (line 366) | def _handler_value_changes(self, service, path, value, text):
    method _execute_value_changes (line 387) | def _execute_value_changes(self, serviceName, objectPath, changes, opt...
    method get_value (line 402) | def get_value(self, serviceName, objectPath, default_value=None):
    method exists (line 415) | def exists(self, serviceName, objectPath):
    method seen (line 429) | def seen(self, serviceName, objectPath):
    method set_value (line 438) | def set_value(self, serviceName, objectPath, value):
    method set_value_async (line 454) | def set_value_async(self, serviceName, objectPath, value,
    method get_service_list (line 473) | def get_service_list(self, classfilter=None):
    method get_device_instance (line 484) | def get_device_instance(self, serviceName):
    method track_value (line 487) | def track_value(self, serviceName, objectPath, callback, *args, **kwar...
  function value_changed_on_dbus (line 525) | def value_changed_on_dbus(dbusServiceName, dbusPath, dict, changes, devi...
  function nameownerchange (line 535) | def nameownerchange(a, b):
  function print_values (line 545) | def print_values(dbusmonitor):
  function main (line 556) | def main():

FILE: velib_python/velib_python/latest/settingsdevice.py
  class SettingsDevice (line 28) | class SettingsDevice(object):
    method __init__ (line 38) | def __init__(self, bus, supportedSettings, eventCallback, name='com.vi...
    method addSettings (line 61) | def addSettings(self, settings):
    method addSetting (line 69) | def addSetting(self, path, value, _min, _max, silent=False, callback=N...
    method handleChangedSetting (line 98) | def handleChangedSetting(self, setting, servicename, path, changes):
    method setDefault (line 107) | def setDefault(self, path):
    method __getitem__ (line 111) | def __getitem__(self, setting):
    method __setitem__ (line 114) | def __setitem__(self, setting, newvalue):

FILE: velib_python/velib_python/latest/ve_utils.py
  class NoVrmPortalIdError (line 14) | class NoVrmPortalIdError(Exception):
  function exit_on_error (line 22) | def exit_on_error(func, *args, **kwargs):
  function get_vrm_portal_id (line 38) | def get_vrm_portal_id():
  function convert_vreg_version_to_readable (line 91) | def convert_vreg_version_to_readable(version):
  function get_free_space (line 124) | def get_free_space(path):
  function _get_sysfs_machine_name (line 136) | def _get_sysfs_machine_name():
  function get_machine_name (line 147) | def get_machine_name():
  function get_product_id (line 169) | def get_product_id():
  function read_file (line 192) | def read_file(path):
  function wrap_dbus_value (line 204) | def wrap_dbus_value(value):
  function unwrap_dbus_value (line 238) | def unwrap_dbus_value(val):
  function add_name_owner_changed_receiver (line 266) | def add_name_owner_changed_receiver(dbus, name_owner_changed, namespace=...

FILE: velib_python/velib_python/latest/vedbus.py
  class VeDbusService (line 60) | class VeDbusService(object):
    method __init__ (line 61) | def __init__(self, servicename, bus=None, register=True):
    method register (line 89) | def register(self):
    method __del__ (line 96) | def __del__(self):
    method get_name (line 107) | def get_name(self):
    method add_path (line 113) | def add_path(self, path, value, description="", writeable=False,
    method add_mandatory_paths (line 133) | def add_mandatory_paths(self, processname, processversion, connection,
    method _value_changed (line 149) | def _value_changed(self, path, newvalue):
    method _item_deleted (line 155) | def _item_deleted(self, path):
    method __getitem__ (line 166) | def __getitem__(self, path):
    method __setitem__ (line 169) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 172) | def __delitem__(self, path):
    method __contains__ (line 176) | def __contains__(self, path):
    method __enter__ (line 179) | def __enter__(self):
    method __exit__ (line 184) | def __exit__(self, *exc):
  class ServiceContext (line 190) | class ServiceContext(object):
    method __init__ (line 191) | def __init__(self, parent):
    method __contains__ (line 195) | def __contains__(self, path):
    method __getitem__ (line 198) | def __getitem__(self, path):
    method __setitem__ (line 201) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 206) | def __delitem__(self, path):
    method flush (line 211) | def flush(self):
    method add_path (line 216) | def add_path(self, path, value, *args, **kwargs):
    method del_tree (line 223) | def del_tree(self, root):
    method get_name (line 230) | def get_name(self):
  class TrackerDict (line 233) | class TrackerDict(defaultdict):
    method __missing__ (line 235) | def __missing__(self, key):
  class VeDbusRootTracker (line 239) | class VeDbusRootTracker(object):
    method __init__ (line 244) | def __init__(self, bus, serviceName):
    method __del__ (line 250) | def __del__(self):
    method add (line 254) | def add(self, i):
    method _items_changed_handler (line 257) | def _items_changed_handler(self, items):
  class VeDbusItemImport (line 297) | class VeDbusItemImport(object):
    method __new__ (line 298) | def __new__(cls, bus, serviceName, path, eventCallback=None, createsig...
    method __init__ (line 316) | def __init__(self, bus, serviceName, path, eventCallback=None, creates...
    method __del__ (line 342) | def __del__(self):
    method _refreshcachedvalue (line 348) | def _refreshcachedvalue(self):
    method path (line 353) | def path(self):
    method serviceName (line 358) | def serviceName(self):
    method get_value (line 365) | def get_value(self):
    method set_value (line 369) | def set_value(self, newvalue):
    method set_default (line 379) | def set_default(self):
    method get_text (line 389) | def get_text(self):
    method exists (line 394) | def exists(self):
    method eventCallback (line 408) | def eventCallback(self):
    method eventCallback (line 412) | def eventCallback(self, eventCallback):
    method _properties_changed_handler (line 417) | def _properties_changed_handler(self, changes):
  class VeDbusTreeExport (line 431) | class VeDbusTreeExport(dbus.service.Object):
    method __init__ (line 432) | def __init__(self, bus, objectPath, service):
    method __del__ (line 437) | def __del__(self):
    method _get_path (line 446) | def _get_path(self):
    method _get_value_handler (line 451) | def _get_value_handler(self, path, get_text=False):
    method GetValue (line 465) | def GetValue(self):
    method GetText (line 470) | def GetText(self):
    method local_get_value (line 473) | def local_get_value(self):
  class VeDbusRootExport (line 476) | class VeDbusRootExport(VeDbusTreeExport):
    method ItemsChanged (line 478) | def ItemsChanged(self, changes):
    method GetItems (line 482) | def GetItems(self):
  class VeDbusItemExport (line 491) | class VeDbusItemExport(dbus.service.Object):
    method __init__ (line 504) | def __init__(self, bus, objectPath, value=None, description=None, writ...
    method __del__ (line 517) | def __del__(self):
    method _get_path (line 528) | def _get_path(self):
    method local_set_value (line 537) | def local_set_value(self, newvalue):
    method _local_set_value (line 542) | def _local_set_value(self, newvalue):
    method local_get_value (line 552) | def local_get_value(self):
    method SetValue (line 563) | def SetValue(self, newvalue):
    method GetDescription (line 598) | def GetDescription(self, language, length):
    method GetValue (line 605) | def GetValue(self):
    method GetText (line 612) | def GetText(self):
    method PropertiesChanged (line 634) | def PropertiesChanged(self, changes):
  class weak_functor (line 640) | class weak_functor:
    method __init__ (line 641) | def __init__(self, f):
    method __call__ (line 645) | def __call__(self, *args, **kargs):

FILE: velib_python/velib_python/v3.34/dbusmonitor.py
  class SystemBus (line 37) | class SystemBus(dbus.bus.BusConnection):
    method __new__ (line 38) | def __new__(cls):
  class SessionBus (line 41) | class SessionBus(dbus.bus.BusConnection):
    method __new__ (line 42) | def __new__(cls):
  class MonitoredValue (line 45) | class MonitoredValue(object):
    method __init__ (line 46) | def __init__(self, value, text, options):
    method __iter__ (line 53) | def __iter__(self):
  class Service (line 56) | class Service(object):
    method __init__ (line 57) | def __init__(self, id, serviceName, deviceInstance):
    method __setitem__ (line 67) | def __setitem__(self, key, value):
    method __getitem__ (line 69) | def __getitem__(self, key):
    method set_seen (line 72) | def set_seen(self, path):
    method seen (line 75) | def seen(self, path):
    method service_class (line 79) | def service_class(self):
  class DbusMonitor (line 82) | class DbusMonitor(object):
    method __init__ (line 84) | def __init__(self, dbusTree, valueChangedCallback=None, deviceAddedCal...
    method make_service (line 138) | def make_service(serviceId, serviceName, deviceInstance):
    method make_monitor (line 142) | def make_monitor(self, service, path, value, text, options):
    method dbus_name_owner_changed (line 146) | def dbus_name_owner_changed(self, name, oldowner, newowner):
    method add_name_owner_changed_receiver (line 156) | def add_name_owner_changed_receiver(dbus, name_owner_changed, namespac...
    method _process_name_owner_changed (line 168) | def _process_name_owner_changed(self, name, oldowner, newowner):
    method scan_dbus_service (line 188) | def scan_dbus_service(self, serviceName):
    method scan_dbus_service_inner (line 203) | def scan_dbus_service_inner(self, serviceName):
    method handler_item_changes (line 287) | def handler_item_changes(self, items, senderId):
    method handler_value_changes (line 309) | def handler_value_changes(self, changes, path, senderId):
    method _handler_value_changes (line 328) | def _handler_value_changes(self, service, path, value, text):
    method _execute_value_changes (line 349) | def _execute_value_changes(self, serviceName, objectPath, changes, opt...
    method get_value (line 364) | def get_value(self, serviceName, objectPath, default_value=None):
    method exists (line 377) | def exists(self, serviceName, objectPath):
    method seen (line 391) | def seen(self, serviceName, objectPath):
    method set_value (line 400) | def set_value(self, serviceName, objectPath, value):
    method set_value_async (line 416) | def set_value_async(self, serviceName, objectPath, value,
    method get_service_list (line 435) | def get_service_list(self, classfilter=None):
    method get_device_instance (line 446) | def get_device_instance(self, serviceName):
    method track_value (line 449) | def track_value(self, serviceName, objectPath, callback, *args, **kwar...
  function value_changed_on_dbus (line 487) | def value_changed_on_dbus(dbusServiceName, dbusPath, dict, changes, devi...
  function nameownerchange (line 497) | def nameownerchange(a, b):
  function print_values (line 507) | def print_values(dbusmonitor):
  function main (line 518) | def main():

FILE: velib_python/velib_python/v3.34/settingsdevice.py
  class SettingsDevice (line 28) | class SettingsDevice(object):
    method __init__ (line 38) | def __init__(self, bus, supportedSettings, eventCallback, name='com.vi...
    method addSettings (line 61) | def addSettings(self, settings):
    method addSetting (line 69) | def addSetting(self, path, value, _min, _max, silent=False, callback=N...
    method handleChangedSetting (line 98) | def handleChangedSetting(self, setting, servicename, path, changes):
    method setDefault (line 107) | def setDefault(self, path):
    method __getitem__ (line 111) | def __getitem__(self, setting):
    method __setitem__ (line 114) | def __setitem__(self, setting, newvalue):

FILE: velib_python/velib_python/v3.34/ve_utils.py
  class NoVrmPortalIdError (line 14) | class NoVrmPortalIdError(Exception):
  function exit_on_error (line 22) | def exit_on_error(func, *args, **kwargs):
  function get_vrm_portal_id (line 38) | def get_vrm_portal_id():
  function convert_vreg_version_to_readable (line 91) | def convert_vreg_version_to_readable(version):
  function get_free_space (line 124) | def get_free_space(path):
  function _get_sysfs_machine_name (line 136) | def _get_sysfs_machine_name():
  function get_machine_name (line 147) | def get_machine_name():
  function get_product_id (line 169) | def get_product_id():
  function read_file (line 192) | def read_file(path):
  function wrap_dbus_value (line 204) | def wrap_dbus_value(value):
  function unwrap_dbus_value (line 238) | def unwrap_dbus_value(val):

FILE: velib_python/velib_python/v3.34/vedbus.py
  class VeDbusService (line 60) | class VeDbusService(object):
    method __init__ (line 61) | def __init__(self, servicename, bus=None):
    method __del__ (line 87) | def __del__(self):
    method add_path (line 101) | def add_path(self, path, value, description="", writeable=False,
    method add_mandatory_paths (line 120) | def add_mandatory_paths(self, processname, processversion, connection,
    method _value_changed (line 136) | def _value_changed(self, path, newvalue):
    method _item_deleted (line 142) | def _item_deleted(self, path):
    method __getitem__ (line 153) | def __getitem__(self, path):
    method __setitem__ (line 156) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 159) | def __delitem__(self, path):
    method __contains__ (line 163) | def __contains__(self, path):
    method __enter__ (line 166) | def __enter__(self):
    method __exit__ (line 171) | def __exit__(self, *exc):
  class ServiceContext (line 177) | class ServiceContext(object):
    method __init__ (line 178) | def __init__(self, parent):
    method __getitem__ (line 182) | def __getitem__(self, path):
    method __setitem__ (line 185) | def __setitem__(self, path, newvalue):
    method flush (line 190) | def flush(self):
  class TrackerDict (line 194) | class TrackerDict(defaultdict):
    method __missing__ (line 196) | def __missing__(self, key):
  class VeDbusRootTracker (line 200) | class VeDbusRootTracker(object):
    method __init__ (line 205) | def __init__(self, bus, serviceName):
    method __del__ (line 211) | def __del__(self):
    method add (line 215) | def add(self, i):
    method _items_changed_handler (line 218) | def _items_changed_handler(self, items):
  class VeDbusItemImport (line 258) | class VeDbusItemImport(object):
    method __new__ (line 259) | def __new__(cls, bus, serviceName, path, eventCallback=None, createsig...
    method __init__ (line 277) | def __init__(self, bus, serviceName, path, eventCallback=None, creates...
    method __del__ (line 303) | def __del__(self):
    method _refreshcachedvalue (line 309) | def _refreshcachedvalue(self):
    method path (line 314) | def path(self):
    method serviceName (line 319) | def serviceName(self):
    method get_value (line 326) | def get_value(self):
    method set_value (line 330) | def set_value(self, newvalue):
    method set_default (line 340) | def set_default(self):
    method get_text (line 350) | def get_text(self):
    method exists (line 355) | def exists(self):
    method eventCallback (line 369) | def eventCallback(self):
    method eventCallback (line 373) | def eventCallback(self, eventCallback):
    method _properties_changed_handler (line 378) | def _properties_changed_handler(self, changes):
  class VeDbusTreeExport (line 392) | class VeDbusTreeExport(dbus.service.Object):
    method __init__ (line 393) | def __init__(self, bus, objectPath, service):
    method __del__ (line 398) | def __del__(self):
    method _get_path (line 407) | def _get_path(self):
    method _get_value_handler (line 412) | def _get_value_handler(self, path, get_text=False):
    method GetValue (line 426) | def GetValue(self):
    method GetText (line 431) | def GetText(self):
    method local_get_value (line 434) | def local_get_value(self):
  class VeDbusRootExport (line 437) | class VeDbusRootExport(VeDbusTreeExport):
    method ItemsChanged (line 439) | def ItemsChanged(self, changes):
    method GetItems (line 443) | def GetItems(self):
  class VeDbusItemExport (line 452) | class VeDbusItemExport(dbus.service.Object):
    method __init__ (line 465) | def __init__(self, bus, objectPath, value=None, description=None, writ...
    method __del__ (line 478) | def __del__(self):
    method _get_path (line 489) | def _get_path(self):
    method local_set_value (line 498) | def local_set_value(self, newvalue):
    method _local_set_value (line 503) | def _local_set_value(self, newvalue):
    method local_get_value (line 513) | def local_get_value(self):
    method SetValue (line 524) | def SetValue(self, newvalue):
    method GetDescription (line 559) | def GetDescription(self, language, length):
    method GetValue (line 566) | def GetValue(self):
    method GetText (line 573) | def GetText(self):
    method PropertiesChanged (line 595) | def PropertiesChanged(self, changes):
  class weak_functor (line 601) | class weak_functor:
    method __init__ (line 602) | def __init__(self, f):
    method __call__ (line 606) | def __call__(self, *args, **kargs):

FILE: velib_python/velib_python/v3.41/dbusmonitor.py
  class SystemBus (line 37) | class SystemBus(dbus.bus.BusConnection):
    method __new__ (line 38) | def __new__(cls):
  class SessionBus (line 41) | class SessionBus(dbus.bus.BusConnection):
    method __new__ (line 42) | def __new__(cls):
  class MonitoredValue (line 45) | class MonitoredValue(object):
    method __init__ (line 46) | def __init__(self, value, text, options):
    method __iter__ (line 53) | def __iter__(self):
  class Service (line 56) | class Service(object):
    method __init__ (line 57) | def __init__(self, id, serviceName, deviceInstance):
    method __setitem__ (line 67) | def __setitem__(self, key, value):
    method __getitem__ (line 69) | def __getitem__(self, key):
    method set_seen (line 72) | def set_seen(self, path):
    method seen (line 75) | def seen(self, path):
    method service_class (line 79) | def service_class(self):
  class DbusMonitor (line 82) | class DbusMonitor(object):
    method __init__ (line 84) | def __init__(self, dbusTree, valueChangedCallback=None, deviceAddedCal...
    method make_service (line 139) | def make_service(serviceId, serviceName, deviceInstance):
    method make_monitor (line 143) | def make_monitor(self, service, path, value, text, options):
    method dbus_name_owner_changed (line 147) | def dbus_name_owner_changed(self, name, oldowner, newowner):
    method _process_name_owner_changed (line 154) | def _process_name_owner_changed(self, name, oldowner, newowner):
    method scan_dbus_service (line 174) | def scan_dbus_service(self, serviceName):
    method scan_dbus_service_inner (line 189) | def scan_dbus_service_inner(self, serviceName):
    method scan_dbus_service_getitems_done (line 286) | def scan_dbus_service_getitems_done(self, serviceName, serviceId, valu...
    method handler_item_changes (line 320) | def handler_item_changes(self, items, senderId):
    method handler_value_changes (line 342) | def handler_value_changes(self, changes, path, senderId):
    method _handler_value_changes (line 361) | def _handler_value_changes(self, service, path, value, text):
    method _execute_value_changes (line 382) | def _execute_value_changes(self, serviceName, objectPath, changes, opt...
    method get_value (line 397) | def get_value(self, serviceName, objectPath, default_value=None):
    method exists (line 410) | def exists(self, serviceName, objectPath):
    method seen (line 424) | def seen(self, serviceName, objectPath):
    method set_value (line 433) | def set_value(self, serviceName, objectPath, value):
    method set_value_async (line 449) | def set_value_async(self, serviceName, objectPath, value,
    method get_service_list (line 468) | def get_service_list(self, classfilter=None):
    method get_device_instance (line 479) | def get_device_instance(self, serviceName):
    method track_value (line 482) | def track_value(self, serviceName, objectPath, callback, *args, **kwar...
  function value_changed_on_dbus (line 520) | def value_changed_on_dbus(dbusServiceName, dbusPath, dict, changes, devi...
  function nameownerchange (line 530) | def nameownerchange(a, b):
  function print_values (line 540) | def print_values(dbusmonitor):
  function main (line 551) | def main():

FILE: velib_python/velib_python/v3.41/settingsdevice.py
  class SettingsDevice (line 28) | class SettingsDevice(object):
    method __init__ (line 38) | def __init__(self, bus, supportedSettings, eventCallback, name='com.vi...
    method addSettings (line 61) | def addSettings(self, settings):
    method addSetting (line 69) | def addSetting(self, path, value, _min, _max, silent=False, callback=N...
    method handleChangedSetting (line 98) | def handleChangedSetting(self, setting, servicename, path, changes):
    method setDefault (line 107) | def setDefault(self, path):
    method __getitem__ (line 111) | def __getitem__(self, setting):
    method __setitem__ (line 114) | def __setitem__(self, setting, newvalue):

FILE: velib_python/velib_python/v3.41/ve_utils.py
  class NoVrmPortalIdError (line 14) | class NoVrmPortalIdError(Exception):
  function exit_on_error (line 22) | def exit_on_error(func, *args, **kwargs):
  function get_vrm_portal_id (line 38) | def get_vrm_portal_id():
  function convert_vreg_version_to_readable (line 91) | def convert_vreg_version_to_readable(version):
  function get_free_space (line 124) | def get_free_space(path):
  function _get_sysfs_machine_name (line 136) | def _get_sysfs_machine_name():
  function get_machine_name (line 147) | def get_machine_name():
  function get_product_id (line 169) | def get_product_id():
  function read_file (line 192) | def read_file(path):
  function wrap_dbus_value (line 204) | def wrap_dbus_value(value):
  function unwrap_dbus_value (line 238) | def unwrap_dbus_value(val):
  function add_name_owner_changed_receiver (line 266) | def add_name_owner_changed_receiver(dbus, name_owner_changed, namespace=...

FILE: velib_python/velib_python/v3.41/vedbus.py
  class VeDbusService (line 60) | class VeDbusService(object):
    method __init__ (line 61) | def __init__(self, servicename, bus=None, register=True):
    method register (line 85) | def register(self):
    method __del__ (line 92) | def __del__(self):
    method get_name (line 103) | def get_name(self):
    method add_path (line 109) | def add_path(self, path, value, description="", writeable=False,
    method add_mandatory_paths (line 129) | def add_mandatory_paths(self, processname, processversion, connection,
    method _value_changed (line 145) | def _value_changed(self, path, newvalue):
    method _item_deleted (line 151) | def _item_deleted(self, path):
    method __getitem__ (line 162) | def __getitem__(self, path):
    method __setitem__ (line 165) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 168) | def __delitem__(self, path):
    method __contains__ (line 172) | def __contains__(self, path):
    method __enter__ (line 175) | def __enter__(self):
    method __exit__ (line 180) | def __exit__(self, *exc):
  class ServiceContext (line 186) | class ServiceContext(object):
    method __init__ (line 187) | def __init__(self, parent):
    method __contains__ (line 191) | def __contains__(self, path):
    method __getitem__ (line 194) | def __getitem__(self, path):
    method __setitem__ (line 197) | def __setitem__(self, path, newvalue):
    method __delitem__ (line 202) | def __delitem__(self, path):
    method flush (line 207) | def flush(self):
    method add_path (line 212) | def add_path(self, path, value, *args, **kwargs):
    method del_tree (line 219) | def del_tree(self, root):
    method get_name (line 226) | def get_name(self):
  class TrackerDict (line 229) | class TrackerDict(defaultdict):
    method __missing__ (line 231) | def __missing__(self, key):
  class VeDbusRootTracker (line 235) | class VeDbusRootTracker(object):
    method __init__ (line 240) | def __init__(self, bus, serviceName):
    method __del__ (line 246) | def __del__(self):
    method add (line 250) | def add(self, i):
    method _items_changed_handler (line 253) | def _items_changed_handler(self, items):
  class VeDbusItemImport (line 293) | class VeDbusItemImport(object):
    method __new__ (line 294) | def __new__(cls, bus, serviceName, path, eventCallback=None, createsig...
    method __init__ (line 312) | def __init__(self, bus, serviceName, path, eventCallback=None, creates...
    method __del__ (line 338) | def __del__(self):
    method _refreshcachedvalue (line 344) | def _refreshcachedvalue(self):
    method path (line 349) | def path(self):
    method serviceName (line 354) | def serviceName(self):
    method get_value (line 361) | def get_value(self):
    method set_value (line 365) | def set_value(self, newvalue):
    method set_default (line 375) | def set_default(self):
    method get_text (line 385) | def get_text(self):
    method exists (line 390) | def exists(self):
    method eventCallback (line 404) | def eventCallback(self):
    method eventCallback (line 408) | def eventCallback(self, eventCallback):
    method _properties_changed_handler (line 413) | def _properties_changed_handler(self, changes):
  class VeDbusTreeExport (line 427) | class VeDbusTreeExport(dbus.service.Object):
    method __init__ (line 428) | def __init__(self, bus, objectPath, service):
    method __del__ (line 433) | def __del__(self):
    method _get_path (line 442) | def _get_path(self):
    method _get_value_handler (line 447) | def _get_value_handler(self, path, get_text=False):
    method GetValue (line 461) | def GetValue(self):
    method GetText (line 466) | def GetText(self):
    method local_get_value (line 469) | def local_get_value(self):
  class VeDbusRootExport (line 472) | class VeDbusRootExport(VeDbusTreeExport):
    method ItemsChanged (line 474) | def ItemsChanged(self, changes):
    method GetItems (line 478) | def GetItems(self):
  class VeDbusItemExport (line 487) | class VeDbusItemExport(dbus.service.Object):
    method __init__ (line 500) | def __init__(self, bus, objectPath, value=None, description=None, writ...
    method __del__ (line 513) | def __del__(self):
    method _get_path (line 524) | def _get_path(self):
    method local_set_value (line 533) | def local_set_value(self, newvalue):
    method _local_set_value (line 538) | def _local_set_value(self, newvalue):
    method local_get_value (line 548) | def local_get_value(self):
    method SetValue (line 559) | def SetValue(self, newvalue):
    method GetDescription (line 594) | def GetDescription(self, language, length):
    method GetValue (line 601) | def GetValue(self):
    method GetText (line 608) | def GetText(self):
    method PropertiesChanged (line 630) | def PropertiesChanged(self, changes):
  class weak_functor (line 636) | class weak_functor:
    method __init__ (line 637) | def __init__(self, f):
    method __call__ (line 641) | def __call__(self, *args, **kargs):
Condensed preview — 68 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (779K chars).
[
  {
    "path": ".github/workflows/latest-tag.yml",
    "chars": 378,
    "preview": "name: Add latest tag to new release\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n  \njobs:\n  run:\n    name:"
  },
  {
    "path": "FileSets/PatchSource/PageSettings.qml",
    "chars": 4497,
    "preview": "import QtQuick 1.1\nimport com.victron.velib 1.0\nimport net.connman 0.1\nimport \"utils.js\" as Utils\n\nMbPage {\n\ttitle: qsTr"
  },
  {
    "path": "FileSets/PatchSource/PageSettings.qml.orig",
    "chars": 4347,
    "preview": "import QtQuick 1.1\nimport com.victron.velib 1.0\nimport net.connman 0.1\nimport \"utils.js\" as Utils\n\nMbPage {\n\ttitle: qsTr"
  },
  {
    "path": "FileSets/PatchSource/PageSettings.qml.patch",
    "chars": 472,
    "preview": "--- /Users/Kevin/GitHub/SetupHelper.copy/FileSets/PatchSource/PageSettings.qml.orig\t2024-05-15 13:06:53\n+++ /Users/Kevin"
  },
  {
    "path": "FileSets/VersionIndependent/MbDisplayDefaultPackage.qml",
    "chars": 2194,
    "preview": "//////// new for PackageManager\n\nimport QtQuick 1.1\nimport com.victron.velib 1.0\nimport \"utils.js\" as Utils\n\nMbItem {\n\ti"
  },
  {
    "path": "FileSets/VersionIndependent/MbDisplayPackageVersion.qml",
    "chars": 4410,
    "preview": "//////// new for PackageManager\n\nimport QtQuick 1.1\nimport com.victron.velib 1.0\nimport \"utils.js\" as Utils\n\nMbItem {\n\ti"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsAddPackageList.qml",
    "chars": 724,
    "preview": "/////// new menu for package version display\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib 1.0"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsPackageAdd.qml",
    "chars": 5653,
    "preview": "/////// new menu for package add edit\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib 1.0\n\nMbPag"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsPackageEdit.qml",
    "chars": 13319,
    "preview": "/////// new menu for package version edit\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib 1.0\n\nM"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsPackageManager.qml",
    "chars": 5121,
    "preview": "/////// new menu for package version display\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib 1.0"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsPackageVersions.qml",
    "chars": 1462,
    "preview": "/////// new menu for package version display\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib 1.0"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsPmBackup.qml",
    "chars": 3255,
    "preview": "/////// new menu for settings backup and restore\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib"
  },
  {
    "path": "FileSets/VersionIndependent/PageSettingsPmInitialize.qml",
    "chars": 2089,
    "preview": "/////// new menu for PackageManager initialize\n\nimport QtQuick 1.1\nimport \"utils.js\" as Utils\nimport com.victron.velib 1"
  },
  {
    "path": "FileSets/fileListPatched",
    "chars": 44,
    "preview": "/opt/victronenergy/gui/qml/PageSettings.qml\n"
  },
  {
    "path": "FileSets/fileListVersionIndependent",
    "chars": 502,
    "preview": "/opt/victronenergy/gui/qml/PageSettingsPackageManager.qml\n/opt/victronenergy/gui/qml/PageSettingsPackageVersions.qml\n/op"
  },
  {
    "path": "HelperResources/CommonResources",
    "chars": 58107,
    "preview": "#!/bin/bash\n\n\n# CommonResources for SetupHelper\n# contains a functions and variables necessary for a setup script to int"
  },
  {
    "path": "HelperResources/DbusSettingsResources",
    "chars": 6746,
    "preview": "#!/bin/bash\n\n# DbusSettingsResources for SetupHelper\n#\n# contains a functions and variables necessary to access dbus Set"
  },
  {
    "path": "HelperResources/EssentialResources",
    "chars": 11985,
    "preview": "#!/bin/bash\n\n# EssentialResources for SetupHelper\n# contains a variables necessary for all setup helper scripts\n#\n# sour"
  },
  {
    "path": "HelperResources/IncludeHelpers",
    "chars": 1646,
    "preview": "#!/bin/sh\n\n# this script sources helper Resources into the setup script\n#\n# for backward compatibility, CommonResources "
  },
  {
    "path": "HelperResources/LogHandler",
    "chars": 105,
    "preview": "# dummy file to prevent failure with older package setup scripts\n\n# logging is now in EssentialResources\n"
  },
  {
    "path": "HelperResources/ServiceResources",
    "chars": 10429,
    "preview": "#!/bin/bash\n\n#!/bin/bash\n# ServiceManager for SetupHelper\n# contains a functions to install and remove a package's servi"
  },
  {
    "path": "HelperResources/VersionResources",
    "chars": 111,
    "preview": "# dummy file to prevent failure with older package setup scripts\n\n# version resources now in EssentialResources"
  },
  {
    "path": "PackageDevelopmentGuidelines.md",
    "chars": 65916,
    "preview": "# SetupHelper package development\n\n**Kevin Windrem kwindrem@icloud.com**\n\n**updated for v6.0**\n\nThis document provides g"
  },
  {
    "path": "PackageManager.py",
    "chars": 154368,
    "preview": "#!/usr/bin/env python\n#!/usr/bin/env python\n#\n#\tPackageManager.py\n#\tKevin Windrem\n#\n#\n# This program is responsible for\n"
  },
  {
    "path": "ReadMe.md",
    "chars": 12764,
    "preview": "# Overview\n\nThe SetupHelper package provides:\n  - a mechanism to automatically reinstall packages following a Venus OS u"
  },
  {
    "path": "blindInstall/SetupHelperVersion",
    "chars": 5,
    "preview": "v9.4\n"
  },
  {
    "path": "blindInstall/blindInstall.sh",
    "chars": 2525,
    "preview": "#!/bin/bash\n\n# this script is part of a \"blind install\" archive which installs SetupHelper\n#\n# Simply inserting media in"
  },
  {
    "path": "blindInstall/post-hook.sh",
    "chars": 638,
    "preview": "#!/bin/bash\n\n# this script is part of a \"blind install\" archive which installs SetupHelper\n#\n# refer to blindInstall.sh "
  },
  {
    "path": "blindInstall/pre-hook.sh",
    "chars": 1659,
    "preview": "#!/bin/bash\n\n# this script is part of a \"blind install\" archive which installs SetupHelper\n# refer to blindInstall.sh fo"
  },
  {
    "path": "blindInstall/rcS.localForUninstall",
    "chars": 2253,
    "preview": "#!/bin/bash\n\n# this script is part of a \"blind UNINSTALL\" archive which UNINSTALLS all packages\n# Packages are not remov"
  },
  {
    "path": "changes",
    "chars": 24759,
    "preview": "v9.4:\n\tadded support for Raspberry PI 5 platform\n\nv9.3:\n\tfixed: patch error on RPI 5\n\nv9.2:\n\tfixed: install failure duri"
  },
  {
    "path": "defaultPackageList",
    "chars": 1083,
    "preview": "# the DEFAULT list of packages managed by SetupHelper\n# actual list is based on what is stored on the system\n# this list"
  },
  {
    "path": "forSetupScript",
    "chars": 231,
    "preview": "#### add the following lines to the package's setup script\n\n#### following line incorporates helper resources into this "
  },
  {
    "path": "genericSetupScript",
    "chars": 768,
    "preview": "#!/bin/bash\n\n# this script will install any package that can use\n# the automated install and uninstall mechanisms provid"
  },
  {
    "path": "gitHubInfo",
    "chars": 16,
    "preview": "kwindrem:latest\n"
  },
  {
    "path": "makeVelib_python",
    "chars": 6204,
    "preview": "#!/bin/bash\n\n\n\n# convert a version string to an integer to make comparisions easier\n#\n#\tNote: copied from VersionResourc"
  },
  {
    "path": "rcS.local",
    "chars": 168,
    "preview": "#!/bin/bash\n\n# SetupHelper reinstall\nif [ -f /data/SetupHelper/reinstallMods ]; then\n\tnohup /data/SetupHelper/reinstallM"
  },
  {
    "path": "reinstallMods",
    "chars": 1610,
    "preview": "#!/bin/sh\n\n# this script is called from /data/rcS.local during system boot\n# it checks to see the PackageManager service"
  },
  {
    "path": "services/PackageManager/log/run",
    "chars": 61,
    "preview": "#!/bin/sh\nexec multilog t s25000 n4 /var/log/PackageManager\n\n"
  },
  {
    "path": "services/PackageManager/run",
    "chars": 62,
    "preview": "#!/bin/sh\nexec 2>&1\nexec /data/SetupHelper/PackageManager.py\n\n"
  },
  {
    "path": "settingsList",
    "chars": 18089,
    "preview": "/Settings/Alarm/Audible\n/Settings/Alarm/System/GridLost\n/Settings/Alarm/Vebus/HighDcCurrent\n/Settings/Alarm/Vebus/HighDc"
  },
  {
    "path": "setup",
    "chars": 1046,
    "preview": "#!/bin/bash\n\n# SetupHelper provides a set of utilities used by other packages to streamline installing and removing pack"
  },
  {
    "path": "updatePackage",
    "chars": 49035,
    "preview": "#!/bin/bash\n\n# this script updates the package contents for all packages specified on the command line\n#\tor in the list "
  },
  {
    "path": "velib_python/dbusmonitor.py",
    "chars": 21963,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n## @package dbus_vrm\n# This code takes care of the D-Bus interface (not "
  },
  {
    "path": "velib_python/oldestVersion",
    "chars": 9,
    "preview": "v3.40~39\n"
  },
  {
    "path": "velib_python/settingsdevice.py",
    "chars": 4716,
    "preview": "import dbus\nimport logging\nimport time\nfrom functools import partial\n\n# Local imports\nfrom vedbus import VeDbusItemImpor"
  },
  {
    "path": "velib_python/ve_utils.py",
    "chars": 8720,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nimport sys\nfrom traceback import print_exc\nfrom os import _exit as os_exi"
  },
  {
    "path": "velib_python/vedbus.py",
    "chars": 24306,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport dbus.service\nimport logging\nimport traceback\nimport os\nimport wea"
  },
  {
    "path": "velib_python/velib_python/latest/dbusmonitor.py",
    "chars": 22051,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n## @package dbus_vrm\n# This code takes care of the D-Bus interface (not "
  },
  {
    "path": "velib_python/velib_python/latest/oldestVersion",
    "chars": 6,
    "preview": "v3.50\n"
  },
  {
    "path": "velib_python/velib_python/latest/settingsdevice.py",
    "chars": 4716,
    "preview": "import dbus\nimport logging\nimport time\nfrom functools import partial\n\n# Local imports\nfrom vedbus import VeDbusItemImpor"
  },
  {
    "path": "velib_python/velib_python/latest/ve_utils.py",
    "chars": 8720,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nimport sys\nfrom traceback import print_exc\nfrom os import _exit as os_exi"
  },
  {
    "path": "velib_python/velib_python/latest/vedbus.py",
    "chars": 24548,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport dbus.service\nimport logging\nimport traceback\nimport os\nimport wea"
  },
  {
    "path": "velib_python/velib_python/v3.34/dbusmonitor.py",
    "chars": 20875,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n## @package dbus_vrm\n# This code takes care of the D-Bus interface (not "
  },
  {
    "path": "velib_python/velib_python/v3.34/oldestVersion",
    "chars": 6,
    "preview": "v3.10\n"
  },
  {
    "path": "velib_python/velib_python/v3.34/settingsdevice.py",
    "chars": 4716,
    "preview": "import dbus\nimport logging\nimport time\nfrom functools import partial\n\n# Local imports\nfrom vedbus import VeDbusItemImpor"
  },
  {
    "path": "velib_python/velib_python/v3.34/ve_utils.py",
    "chars": 7973,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nimport sys\nfrom traceback import print_exc\nfrom os import _exit as os_exi"
  },
  {
    "path": "velib_python/velib_python/v3.34/vedbus.py",
    "chars": 23359,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport dbus.service\nimport logging\nimport traceback\nimport os\nimport wea"
  },
  {
    "path": "velib_python/velib_python/v3.41/dbusmonitor.py",
    "chars": 21963,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n## @package dbus_vrm\n# This code takes care of the D-Bus interface (not "
  },
  {
    "path": "velib_python/velib_python/v3.41/oldestVersion",
    "chars": 6,
    "preview": "v3.40\n"
  },
  {
    "path": "velib_python/velib_python/v3.41/settingsdevice.py",
    "chars": 4716,
    "preview": "import dbus\nimport logging\nimport time\nfrom functools import partial\n\n# Local imports\nfrom vedbus import VeDbusItemImpor"
  },
  {
    "path": "velib_python/velib_python/v3.41/ve_utils.py",
    "chars": 8720,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\nimport sys\nfrom traceback import print_exc\nfrom os import _exit as os_exi"
  },
  {
    "path": "velib_python/velib_python/v3.41/vedbus.py",
    "chars": 24306,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport dbus.service\nimport logging\nimport traceback\nimport os\nimport wea"
  },
  {
    "path": "version",
    "chars": 5,
    "preview": "v9.4\n"
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the kwindrem/SetupHelper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 68 files (700.4 KB), approximately 185.9k tokens, and a symbol index with 607 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!