Exemplo n.º 1
0
        logMessage("Warning: minimum BrewPi version compatible with this script is " +
                   compatibleHwVersion +
                   " but version number received is " + hwVersion.toString())
    if int(hwVersion.log) != int(expandLogMessage.getVersion()):
        logMessage("Warning: version number of local copy of logMessages.h " +
                   "does not match log version number received from controller." +
                   "controller version = " + str(hwVersion.log) +
                   ", local copy version = " + str(expandLogMessage.getVersion()))

bg_ser = None

if ser is not None:
    ser.flush()

    # set up background serial processing, which will continuously read data from serial and put whole lines in a queue
    bg_ser = BackGroundSerial(ser)
    bg_ser.start()
    # request settings from controller, processed later when reply is received
    bg_ser.write('s')  # request control settings cs
    bg_ser.write('c')  # request control constants cc
    # answer from controller is received asynchronously later.

# create a listening socket to communicate with PHP
is_windows = sys.platform.startswith('win')
useInetSocket = bool(config.get('useInetSocket', is_windows))
if useInetSocket:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socketPort = config.get('socketPort', 6332)
    s.bind((config.get('socketHost', 'localhost'), int(socketPort)))
    logMessage('Bound to TCP socket on port %d ' % int(socketPort))
Exemplo n.º 2
0
def updateFromGitHub(userInput,
                     beta,
                     useDfu,
                     restoreSettings=True,
                     restoreDevices=True):
    import BrewPiUtil as util
    from gitHubReleases import gitHubReleases
    import brewpiVersion
    import programController as programmer

    configFile = util.scriptPath() + '/settings/config.cfg'
    config = util.readCfgWithDefaults(configFile)

    printStdErr(
        "Stopping any running instances of BrewPi to check/update controller..."
    )
    quitBrewPi(config['wwwPath'])

    hwVersion = None
    shield = None
    board = None
    family = None

    ### Get version number
    printStdErr("\nChecking current firmware version...")
    bg_ser = BackGroundSerial(config.get('port', 'auto'))
    hwVersion = brewpiVersion.getVersionFromSerial(bg_ser)

    if hwVersion is not None:
        family = hwVersion.family
        shield = hwVersion.shield
        board = hwVersion.board
    else:
        printStdErr(
            "Unable to receive version from controller.\n"
            "Is your controller unresponsive and do you wish to try restoring your firmware? [y/N]: "
        )
        choice = raw_input()
        if not any(choice == x
                   for x in ["yes", "Yes", "YES", "yes", "y", "Y"]):
            printStdErr(
                "Please make sure your controller is connected properly and try again."
            )
            return 0
        port = autoSerial.detect_port()
        if not port:
            printStdErr(
                "Could not find compatible device in available serial ports.")
            return 0
        name = autoSerial.recognized_device_name(port)
        if "Particle" in name:
            family = "Particle"
            if "P1" in name:
                board = 'p1'
            elif "Photon" in name:
                board = 'photon'
            elif "Core" in name:
                board = 'core'
        elif "Arduino" in name:
            family = "Arduino"
            if "Leonardo" in name:
                board = 'leonardo'
            elif "Uno" in name:
                board = 'uno'

        if board is None:
            printStdErr(
                "Unable to connect to controller, perhaps it is disconnected or otherwise unavailable."
            )
            return -1
        else:
            printStdErr("Will try to restore the firmware on your %s" % name)
            if family == "Arduino":
                printStdErr(
                    "Assuming a Rev C shield. If this is not the case, please program your Arduino manually"
                )
                shield = 'RevC'
            else:
                printStdErr(
                    "Please put your controller in DFU mode now by holding the setup button during reset, until the LED blinks yellow."
                )
                printStdErr("Press Enter when ready.")
                choice = raw_input()
                useDfu = True  # use dfu mode when board is not responding to serial

    bg_ser.stop()
    del bg_ser

    if hwVersion:
        printStdErr("Current firmware version on controller: " +
                    hwVersion.toString())
    else:
        restoreDevices = False
        restoreSettings = False

    printStdErr("\nChecking GitHub for available release...")
    releases = gitHubReleases("https://api.github.com/repos/BrewPi/firmware")

    availableTags = releases.getTags(beta)
    stableTags = releases.getTags(False)
    compatibleTags = []
    for tag in availableTags:
        url = None
        if family == "Arduino":
            url = releases.getBinUrl(tag, [board, shield, ".hex"])
        elif family == "Spark" or family == "Particle":
            url = releases.getBinUrl(tag, [board, 'brewpi', '.bin'])
        if url is not None:
            compatibleTags.append(tag)

    if len(compatibleTags) == 0:
        printStdErr("No compatible releases found for %s %s" % (family, board))
        return -1

    # default tag is latest stable tag, or latest unstable tag if no stable tag is found
    default_choice = next(
        (i for i, t in enumerate(compatibleTags) if t in stableTags), 0)
    tag = compatibleTags[default_choice]

    if userInput:
        print("\nAvailable releases:\n")
        for i, menu_tag in enumerate(compatibleTags):
            print("[%d] %s" % (i, menu_tag))
        print("[" + str(len(compatibleTags)) + "] Cancel firmware update")
        num_choices = len(compatibleTags)
        while 1:
            try:
                choice = raw_input(
                    "Enter the number [0-%d] of the version you want to program [default = %d (%s)]: "
                    % (num_choices, default_choice, tag))
                if choice == "":
                    break
                else:
                    selection = int(choice)
            except ValueError:
                print("Use the number! [0-%d]" % num_choices)
                continue
            if selection == num_choices:
                return False  # choice = skip updating
            try:
                tag = compatibleTags[selection]
            except IndexError:
                print("Not a valid choice. Try again")
                continue
            break
    else:
        printStdErr("Latest version on GitHub: " + tag)

    if hwVersion is not None and not hwVersion.isNewer(tag):
        if hwVersion.isEqual(tag):
            printStdErr("You are already running version %s." % tag)
        else:
            printStdErr("Your current version is newer than %s." % tag)

        if userInput:
            printStdErr(
                "If you are encountering problems, you can reprogram anyway."
                " Would you like to do this?"
                "\nType yes to reprogram or just press enter to keep your current firmware: "
            )
            choice = raw_input()
            if not any(choice == x
                       for x in ["yes", "Yes", "YES", "yes", "y", "Y"]):
                return 0
        else:
            printStdErr("No update needed. Exiting.")
            exit(0)

    if hwVersion is not None and userInput:
        printStdErr(
            "Would you like me to try to restore you settings after programming? [Y/n]: "
        )
        choice = raw_input()
        if not any(choice == x
                   for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
            restoreSettings = False
        printStdErr(
            "Would you like me to try to restore your configured devices after programming? [Y/n]: "
        )
        choice = raw_input()
        if not any(choice == x
                   for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
            restoreDevices = False

    printStdErr("Downloading firmware...")
    localFileName = None
    system1 = None
    system2 = None

    if family == "Arduino":
        localFileName = releases.getBin(tag, [board, shield, ".hex"])
    elif family == "Spark" or family == "Particle":
        localFileName = releases.getBin(tag, [board, 'brewpi', '.bin'])
    else:
        printStdErr("Error: Device family {0} not recognized".format(family))
        return -1

    if board == "photon" or board == "p1" and useDfu:
        if hwVersion:
            oldVersion = hwVersion.version.vstring
        else:
            oldVersion = "0.0.0"
        latestSystemTag = releases.getLatestTagForSystem(board,
                                                         prerelease=beta,
                                                         since=oldVersion)
        if latestSystemTag is not None:
            printStdErr(
                "Updated system firmware for the {0} found in release {1}".
                format(board, latestSystemTag))
            system1 = releases.getBin(latestSystemTag,
                                      [board, 'system-part1', '.bin'])
            system2 = releases.getBin(latestSystemTag,
                                      [board, 'system-part2', '.bin'])
            if system1:
                printStdErr("Downloaded new system firmware to:\n")
                printStdErr("{0}\nand\n".format(system1))
                if system2:
                    printStdErr("{0}\n".format(system2))
                else:
                    printStdErr(
                        "Error: system firmware part2 not found in release")
                    return -1
        else:
            printStdErr("Photon system firmware is up to date.\n")

    if localFileName:
        printStdErr("Latest firmware downloaded to:\n" + localFileName)
    else:
        printStdErr("Downloading firmware failed")
        return -1

    printStdErr("\nUpdating firmware...\n")
    result = programmer.programController(config, board, localFileName,
                                          system1, system2, useDfu, {
                                              'settings': restoreSettings,
                                              'devices': restoreDevices
                                          })
    util.removeDontRunFile(config['wwwPath'] + "/do_not_run_brewpi")
    return result
Exemplo n.º 3
0
    if hwVersion.family == 'Arduino':
        exit(
            "\n ERROR: the newest version of BrewPi is not compatible with Arduino. \n"
            +
            "You can use our legacy branch with your Arduino, in which we only include the backwards compatible changes. \n"
            +
            "To change to the legacy branch, run: sudo ~/brewpi-tools/updater.py --ask , and choose the legacy branch."
        )

bg_ser = None

if ser is not None:
    ser.flush()

    # set up background serial processing, which will continuously read data from serial and put whole lines in a queue
    bg_ser = BackGroundSerial(ser)
    bg_ser.start()
    # request settings from controller, processed later when reply is received
    bg_ser.write('s')  # request control settings cs
    bg_ser.write('c')  # request control constants cc
    bg_ser.write('v')  # request control variables cv
    # answer from controller is received asynchronously later.

# create a listening socket to communicate with PHP
is_windows = sys.platform.startswith('win')
useInetSocket = bool(config.get('useInetSocket', is_windows))
if useInetSocket:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    socketPort = config.get('socketPort', 6332)
    s.bind((config.get('socketHost', 'localhost'), int(socketPort)))
Exemplo n.º 4
0
        config = util.configSet(configFile, 'dataLogging', 'active')
        return {
            'status': 0,
            'statusMessage': "Successfully continued logging."
        }
    else:
        return {'status': 1, 'statusMessage': "Logging was not paused."}


logMessage("Notification: Script started for beer '" +
           urllib.unquote(config['beerName']) + "'")

logMessage("Connecting to controller...")

# set up background serial processing, which will continuously read data from serial and put whole lines in a queue
bg_ser = BackGroundSerial(config.get('port', 'auto'))

hwVersion = brewpiVersion.getVersionFromSerial(bg_ser)
if hwVersion is None:
    logMessage(
        "Warning: Cannot receive version number from controller. " +
        "Check your port setting in the Maintenance Panel or in settings/config.cfg."
    )
else:
    logMessage("Found " + hwVersion.toExtendedString())
    if LooseVersion(hwVersion.toString()) < LooseVersion(compatibleHwVersion):
        logMessage(
            "Warning: minimum BrewPi version compatible with this script is " +
            compatibleHwVersion + " but version number received is " +
            hwVersion.toString())
    if int(hwVersion.log) != int(expandLogMessage.getVersion()):
Exemplo n.º 5
0
def resumeLogging():
    global config
    logMessage("Continued logging data, as requested in web interface.")
    if config['dataLogging'] == 'paused':
        config = util.configSet(configFile, 'dataLogging', 'active')
        return {'status': 0, 'statusMessage': "Successfully continued logging."}
    else:
        return {'status': 1, 'statusMessage': "Logging was not paused."}

logMessage("Notification: Script started for beer '" + urllib.unquote(config['beerName']) + "'")

logMessage("Connecting to controller...") 

# set up background serial processing, which will continuously read data from serial and put whole lines in a queue
bg_ser = BackGroundSerial(config.get('port', 'auto'))

hwVersion = brewpiVersion.getVersionFromSerial(bg_ser)
if hwVersion is None:
    logMessage("Warning: Cannot receive version number from controller. " +
               "Check your port setting in the Maintenance Panel or in settings/config.cfg.")
else:
    logMessage("Found " + hwVersion.toExtendedString())
    if LooseVersion( hwVersion.toString() ) < LooseVersion(compatibleHwVersion):
        logMessage("Warning: minimum BrewPi version compatible with this script is " +
                   compatibleHwVersion +
                   " but version number received is " + hwVersion.toString())
    if int(hwVersion.log) != int(expandLogMessage.getVersion()):
        logMessage("Warning: version number of local copy of logMessages.h " +
                   "does not match log version number received from controller." +
                   "controller version = " + str(hwVersion.log) +
Exemplo n.º 6
0
def updateFromGitHub(userInput, beta, useDfu, restoreSettings = True, restoreDevices = True):
    import BrewPiUtil as util
    from gitHubReleases import gitHubReleases
    import brewpiVersion
    import programController as programmer

    configFile = util.scriptPath() + '/settings/config.cfg'
    config = util.readCfgWithDefaults(configFile)

    printStdErr("Stopping any running instances of BrewPi to check/update controller...")
    quitBrewPi(config['wwwPath'])

    hwVersion = None
    shield = None
    board = None
    family = None
    
    ### Get version number
    printStdErr("\nChecking current firmware version...")
    bg_ser = BackGroundSerial(config.get('port', 'auto'))    
    hwVersion = brewpiVersion.getVersionFromSerial(bg_ser)
    
    if hwVersion is not None:
        family = hwVersion.family
        shield = hwVersion.shield
        board = hwVersion.board
    else:
        printStdErr("Unable to receive version from controller.\n"
                    "Is your controller unresponsive and do you wish to try restoring your firmware? [y/N]: ")
        choice = raw_input()
        if not any(choice == x for x in ["yes", "Yes", "YES", "yes", "y", "Y"]):
            printStdErr("Please make sure your controller is connected properly and try again.")
            return 0
        port, name = autoSerial.detect_port()
        if not port:
            printStdErr("Could not find compatible device in available serial ports.")
            return 0
        if "Particle" in name:
            family = "Particle"
            if "P1" in name:
                board = 'p1'
            elif "Photon" in name:
                board = 'photon'
            elif "Core" in name:
                board = 'core'
        elif "Arduino" in name:
            family = "Arduino"
            if "Leonardo" in name:
                board = 'leonardo'
            elif "Uno" in name:
                board = 'uno'

        if board is None:
            printStdErr("Unable to connect to controller, perhaps it is disconnected or otherwise unavailable.")
            return -1
        else:
            printStdErr("Will try to restore the firmware on your %s" % name)
            if family == "Arduino":
                printStdErr("Assuming a Rev C shield. If this is not the case, please program your Arduino manually")
                shield = 'RevC'
            else:
                printStdErr("Please put your controller in DFU mode now by holding the setup button during reset, until the LED blinks yellow.")
                printStdErr("Press Enter when ready.")
                choice = raw_input()
                useDfu = True # use dfu mode when board is not responding to serial

    bg_ser.stop()
    del bg_ser

    if hwVersion:
        printStdErr("Current firmware version on controller: " + hwVersion.toString())
    else:
        restoreDevices = False
        restoreSettings = False

    printStdErr("\nChecking GitHub for available release...")
    releases = gitHubReleases("https://api.github.com/repos/BrewPi/firmware")

    availableTags = releases.getTags(beta)
    stableTags = releases.getTags(False)
    compatibleTags = []
    for tag in availableTags:
        url = None
        if family == "Arduino":
            url = releases.getBinUrl(tag, [board, shield, ".hex"])
        elif family == "Spark" or family == "Particle":
            url = releases.getBinUrl(tag, [board, 'brewpi', '.bin'])
        if url is not None:
            compatibleTags.append(tag)

    if len(compatibleTags) == 0:
        printStdErr("No compatible releases found for %s %s" % (family, board))
        return -1

    # default tag is latest stable tag, or latest unstable tag if no stable tag is found
    default_choice = next((i for i, t in enumerate(compatibleTags) if t in stableTags), 0)
    tag = compatibleTags[default_choice]

    if userInput:
        print("\nAvailable releases:\n")
        for i, menu_tag in enumerate(compatibleTags):
            print("[%d] %s" % (i, menu_tag))
        print ("[" + str(len(compatibleTags)) + "] Cancel firmware update")
        num_choices = len(compatibleTags)
        while 1:
            try:
                choice = raw_input("Enter the number [0-%d] of the version you want to program [default = %d (%s)]: " %
                                   (num_choices, default_choice, tag))
                if choice == "":
                    break
                else:
                    selection = int(choice)
            except ValueError:
                print("Use the number! [0-%d]" % num_choices)
                continue
            if selection == num_choices:
                return False # choice = skip updating
            try:
                tag = compatibleTags[selection]
            except IndexError:
                print("Not a valid choice. Try again")
                continue
            break
    else:
        printStdErr("Latest version on GitHub: " + tag)

    if hwVersion is not None and not hwVersion.isNewer(tag):
        if hwVersion.isEqual(tag):
            printStdErr("You are already running version %s." % tag)
        else:
            printStdErr("Your current version is newer than %s." % tag)

        if userInput:
            printStdErr("If you are encountering problems, you can reprogram anyway."
                        " Would you like to do this?"
                        "\nType yes to reprogram or just press enter to keep your current firmware: ")
            choice = raw_input()
            if not any(choice == x for x in ["yes", "Yes", "YES", "yes", "y", "Y"]):
                return 0
        else:
            printStdErr("No update needed. Exiting.")
            exit(0)


    if hwVersion is not None and userInput:
        printStdErr("Would you like me to try to restore you settings after programming? [Y/n]: ")
        choice = raw_input()
        if not any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
            restoreSettings = False
        printStdErr("Would you like me to try to restore your configured devices after programming? [Y/n]: ")
        choice = raw_input()
        if not any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
            restoreDevices = False

    printStdErr("Downloading firmware...")
    localFileName = None
    system1 = None
    system2 = None

    if family == "Arduino":
        localFileName = releases.getBin(tag, [board, shield, ".hex"])
    elif family == "Spark" or family == "Particle":
        localFileName = releases.getBin(tag, [board, 'brewpi', '.bin'])
    else:
        printStdErr("Error: Device family {0} not recognized".format(family))
        return -1

    if board == "photon" or board == "p1" and useDfu:
        if hwVersion:
            oldVersion = hwVersion.version.vstring
        else:
            oldVersion = "0.0.0"
        latestSystemTag = releases.getLatestTagForSystem(board, prerelease=beta, since=oldVersion)
        if latestSystemTag is not None:
            printStdErr("Updated system firmware for the {0} found in release {1}".format(board, latestSystemTag))
            system1 = releases.getBin(latestSystemTag, [board, 'system-part1', '.bin'])
            system2 = releases.getBin(latestSystemTag, [board, 'system-part2', '.bin'])
            if system1:
                printStdErr("Downloaded new system firmware to:\n")
                printStdErr("{0}\nand\n".format(system1))
                if system2:
                    printStdErr("{0}\n".format(system2))
                else:
                    printStdErr("Error: system firmware part2 not found in release")
                    return -1
        else:
            printStdErr("Photon system firmware is up to date.\n")

    if localFileName:
        printStdErr("Latest firmware downloaded to:\n" + localFileName)
    else:
        printStdErr("Downloading firmware failed")
        return -1

    printStdErr("\nUpdating firmware...\n")
    result = programmer.programController(config, board, localFileName, system1, system2, useDfu,
                                          {'settings': restoreSettings, 'devices': restoreDevices})
    util.removeDontRunFile(config['wwwPath'] + "/do_not_run_brewpi")
    return result
 def open_bg_serial(self):
     self.close_serial()
     if self.bg_ser is None:
         self.bg_ser = BackGroundSerial(self.config.get('port', 'auto'))
class SerialProgrammer:
    @staticmethod
    def create(config, boardType):
        if boardType=='core':
            msg_map["a"] = "Spark Core"
            programmer = SparkProgrammer(config, boardType)
        elif boardType == 'photon':
            msg_map["a"] = "Photon"
            programmer = SparkProgrammer(config, boardType)
        elif boardType == 'p1':
            msg_map["a"] = "P1"
            programmer = SparkProgrammer(config, boardType)
        else:
            programmer = None
        return programmer

    def __init__(self, config):
        self.config = config
        self.restoreSettings = False
        self.restoreDevices = False
        self.ser = None
        self.bg_ser = None
        self.versionNew = None
        self.versionOld = None
        self.oldSettings = {}

    def program(self, hexFile, system1File, system2File, useDfu, restoreWhat):
        printStdErr("****    %(a)s Program script started    ****" % msg_map)

        self.parse_restore_settings(restoreWhat)

        self.open_bg_serial()
        if self.restoreSettings or self.restoreDevices:
            printStdErr("Checking old version before programming.")
                        
            # request all settings from board before programming
            if self.fetch_current_version():
                self.retrieve_settings_from_serial()
                self.save_settings_to_file()

        self.close_bg_serial()                
        
        if useDfu:
            printStdErr("\nTrying to automatically reboot into DFU mode and update your firmware.")
            printStdErr("\nIf the Photon does not reboot into DFU mode automatically, please put it in DFU mode manually.")
            
            self.close_all_serial()

            myDir = os.path.dirname(os.path.abspath(__file__))
            flashDfuPath = os.path.join(myDir, 'utils', 'flashDfu.py')
            command = sys.executable + ' ' + flashDfuPath + " --autodfu --noreset --file={0}".format(os.path.abspath(hexFile))
            if system1File is not None and system2File is not None:
                systemParameters = " --system1={0} --system2={1}".format(system1File, system2File)
                command = command + systemParameters
            if platform.system() == "Linux":
                command =  'sudo ' + command
            printStdErr("Running command: " + command)
            process = subprocess.Popen(command, shell=True)
            process.wait()

            printStdErr("\nUpdating firmware over DFU finished\n")

        else:
            if not self.open_serial(self.config, 57600, 0.2):
                printStdErr("Could not open serial port to flash the firmware.")
                return False

            if system1File:
                printStdErr("Flashing system part 1.")
                if not self.flash_file(system1File):
                    return False

                waitForReset(15)
                if not self.open_serial_with_retry(self.config, 57600, 0.2):
                    printStdErr("Error opening serial port after flashing system part 1. Program script will exit.")
                    printStdErr("If your device stopped working, use flashDfu.py to restore it.")
                    return False

            if system2File:
                printStdErr("Flashing system part 2.")
                if not self.flash_file(system2File):
                    return False

                waitForReset(15)
                if not self.open_serial_with_retry(self.config, 57600, 0.2):
                    printStdErr("Error opening serial port after flashing system part 2. Program script will exit.")
                    printStdErr("If your device stopped working, use flashDfu.py to restore it.")
                    return False

            if(hexFile):
                if not self.flash_file(hexFile):
                    return False
                waitForReset(15)

            self.close_serial()
        
        printStdErr("Now checking new version.")
        self.open_bg_serial()

        # request all settings from board before programming
        self.fetch_new_version()
        self.reset_settings()
        if self.restoreSettings or self.restoreDevices:
            printStdErr("Now checking which settings and devices can be restored...")
        if self.versionNew is None:
            printStdErr(("Warning: Cannot receive version number from controller after programming. "
                         "\nSomething must have gone wrong. Restoring settings/devices settings failed.\n"))
            return 0

        if not self.versionOld and (self.restoreSettings or self.restoreDevices):
            printStdErr("Could not receive valid version number from old board, " +
                        "No settings/devices are restored.")
            return 0

        if self.restoreSettings:
            printStdErr("Trying to restore compatible settings from " +
                        self.versionOld.toString() + " to " + self.versionNew.toString())

            if(self.versionNew.isNewer("0.2")):
                printStdErr("Sorry, settings can only be restored when updating to BrewPi 0.2.0 or higher")
                self.restoreSettings = False

        if self.restoreSettings:
            self.restore_settings()

        if self.restoreDevices:
            self.restore_devices()

        printStdErr("****    Program script done!    ****")
        self.close_bg_serial()
        return 1

    def parse_restore_settings(self, restoreWhat):
        restoreSettings = False
        restoreDevices = False
        if 'settings' in restoreWhat:
            if restoreWhat['settings']:
                restoreSettings = True
        if 'devices' in restoreWhat:
            if restoreWhat['devices']:
                restoreDevices = True
        # Even when restoreSettings and restoreDevices are set to True here,
        # they might be set to false due to version incompatibility later

        printStdErr("Settings will " + ("" if restoreSettings else "not ") + "be restored" +
                    (" if possible" if restoreSettings else ""))
        printStdErr("Devices will " + ("" if restoreDevices else "not ") + "be restored" +
                    (" if possible" if restoreSettings else ""))
        self.restoreSettings = restoreSettings
        self.restoreDevices = restoreDevices

    def open_serial(self, config, baud, timeout):
        self.close_bg_serial()
        if self.ser is None:
            self.ser = util.setupSerial(config, baud, timeout)
            if self.ser is None:
                return False
        return True

    def open_bg_serial(self):
        self.close_serial()
        if self.bg_ser is None:
            self.bg_ser = BackGroundSerial(self.config.get('port', 'auto'))
    
    def close_serial(self):
        if self.ser:
            self.ser.close()
            self.ser = None

    def close_bg_serial(self):
        if self.bg_ser:
            self.bg_ser.stop()
            self.bg_ser = None
    
    def close_all_serial(self):
        self.close_bg_serial()

    def open_serial_with_retry(self, config, baud, timeout):
        # reopen serial port
        retries = 30
        self.ser = None
        while retries:
            time.sleep(1)
            if self.open_serial(config, baud, timeout):
                return True
            retries -= 1
        return False

    def fetch_version(self, msg):
        self.open_bg_serial()
        version = brewpiVersion.getVersionFromSerial(self.bg_ser)
        if version is None:
            printStdErr("Warning: Cannot receive version number from controller. It will be reset to defaults.")
        return version

    def fetch_current_version(self):
        self.versionOld = self.fetch_version("Checking current version: ")
        return self.versionOld

    def fetch_new_version(self):
        self.versionNew = self.fetch_version("Checking new version: ")
        return self.versionNew

    def retrieve_settings_from_serial(self):
        self.open_bg_serial()
        self.oldSettings.clear()
        printStdErr("Requesting old settings from %(a)s..." % msg_map)
        expected_responses = 2
        if not self.versionOld.isNewer("0.2.0"):  # versions older than 2.0.0 did not have a device manager
            expected_responses += 1
            self.bg_ser.writeln("d{}")  # installed devices
            time.sleep(1)
        self.bg_ser.writeln("c")  # control constants
        self.bg_ser.writeln("s")  # control settings
        start = time.time()
        timeout = False
        while expected_responses > 0 and not timeout:
            line = self.bg_ser.read_line()
            if line:
                if line[0] == 'C':
                    expected_responses -= 1
                    self.oldSettings['controlConstants'] = json_decode_response(line)
                elif line[0] == 'S':
                    expected_responses -= 1
                    self.oldSettings['controlSettings'] = json_decode_response(line)
                elif line[0] == 'd':
                    expected_responses -= 1
                    self.oldSettings['installedDevices'] = json_decode_response(line)
            time.sleep(0.2)
            if time.time() - start > 10:
                timeout = True

        if(timeout):
            printStdErr("Timeout when requesting old settings from %(a)s..." % msg_map)
            printStdErr("Not all settings will be restored")

    def save_settings_to_file(self):
        oldSettingsFileName = 'settings-' + time.strftime("%b-%d-%Y-%H-%M-%S") + '.json'
        settingsBackupDir = util.scriptPath() + '/settings/controller-backup/'
        if not os.path.exists(settingsBackupDir):
            os.makedirs(settingsBackupDir, 0777)

        oldSettingsFilePath = os.path.join(settingsBackupDir, oldSettingsFileName)
        oldSettingsFile = open(oldSettingsFilePath, 'wb')
        oldSettingsFile.write(json.dumps(self.oldSettings))
        oldSettingsFile.truncate()
        oldSettingsFile.close()
        os.chmod(oldSettingsFilePath, 0777) # make sure file can be accessed by all in case the script ran as root
        printStdErr("Saved old settings to file " + oldSettingsFileName)

    def delay(self, countDown):
        while countDown > 0:
            time.sleep(1)
            countDown -= 1
            printStdErr("Back up in " + str(countDown) + "...")

    def flash_file(self, hexFile):
        raise Exception("not implemented")

    def reset_settings(self, setTestMode = False):
        printStdErr("Resetting EEPROM to default settings")
        self.open_bg_serial()
        self.bg_ser.writeln('E')
        if setTestMode:
            self.bg_ser.writeln('j{mode:t}')
        
        start = time.time()
        # read log messages from controller
        while time.time() - start < 10: 
            # read all lines on serial interface
            message = self.bg_ser.read_message()
            if message:  # message available?
                printStdErr(message)
                if "RESET" in message:
                    break

    def restore_settings(self):
        oldSettingsDict = self.get_combined_settings_dict(self.oldSettings)
        ms = MigrateSettings()
        restored, omitted = ms.getKeyValuePairs(oldSettingsDict,
                                                self.versionOld.toString(),
                                                self.versionNew.toString())

        printStdErr("Migrating these settings: " + json.dumps(restored.items()))
        printStdErr("Omitting these settings: " + json.dumps(omitted.items()))

        self.send_restored_settings(restored)


    def get_combined_settings_dict(self, oldSettings):
        combined = oldSettings.get('controlConstants').copy() # copy keys/values from controlConstants
        combined.update(oldSettings.get('controlSettings')) # add keys/values from controlSettings
        return combined

    def send_restored_settings(self, restoredSettings):
        for key in restoredSettings:
            self.open_bg_serial()
            setting =  restoredSettings[key]
            command = "j{" + json.dumps(key) + ":" + json.dumps(setting) + "}\n"
            self.bg_ser.write(command)
            time.sleep(0.1)
        
            message = self.bg_ser.read_message()
            if message:
                printStdErr(message)

        time.sleep(1)
        while True:  # read remaining log messages
            message = self.bg_ser.read_message()
            if message:
                printStdErr(message)
            else:
                break

    def restore_devices(self):
        self.open_bg_serial()

        oldDevices = self.oldSettings.get('installedDevices')
        if oldDevices:
            printStdErr("Now trying to restore previously installed devices: " + str(oldDevices))
        else:
            printStdErr("No devices to restore!")
            return

        detectedDevices = None
        for device in oldDevices:
            printStdErr("Restoring device: " + json.dumps(device))
            if "a" in device.keys(): # check for sensors configured as first on bus
                if int(device['a'], 16) == 0:
                    printStdErr("OneWire sensor was configured to autodetect the first sensor on the bus, " +
                                "but this is no longer supported. " +
                                "We'll attempt to automatically find the address and add the sensor based on its address")
                    if detectedDevices is None:
                        self.bg_ser.write("h{}\n")  # installed devices
                        time.sleep(1)
                        # get list of detected devices
                        for line in self.bg_ser:
                            if line[0] == 'h':
                                detectedDevices = json_decode_response(line)

                    for detectedDevice in detectedDevices:
                        if device['p'] == detectedDevice['p']:
                            device['a'] = detectedDevice['a'] # get address from sensor that was first on bus

            self.bg_ser.write("U" + json.dumps(device) + "\n")

            requestTime = time.time()
            # read log messages from controller
            while 1:  # read all lines on serial interface
                line = self.bg_ser.read_line()
                if line:  # line available?
                    if line[0] == 'U':
                        printStdErr(("%(a)s reports: device updated to: " % msg_map) + line[2:])
                        break
                message = self.bg_ser.read_message()
                if message:
                    printStdErr(message)
                if time.time() - requestTime > 5:  # wait max 5 seconds for an answer
                    break
        printStdErr("Restoring installed devices done!")