def run_init_mode(self) -> None:
        """Runs init mode."""
        self.logger.debug("Entered INIT")

        # Connect to pubsub service
        self.pubsub = PubSub(
            self,
            on_connect,
            on_disconnect,
            on_publish,
            on_message,
            on_subscribe,
            on_log,
        )

        # Initialize iot connection state
        self.is_connected = False

        # Initialize registration state
        self.is_registered = registration.is_registered()
        self.device_id = registration.device_id()
        self.verification_code = registration.verification_code()

        # Check if network is connected
        if not self.network_is_connected:
            self.logger.info("Waiting for network to come online")

        # Loop forever
        while True:

            # Check if network is connected
            if self.network_is_connected:
                self.logger.debug("Network came online")
                break

            # Update every 100ms
            time.sleep(0.1)

        # Give the network time to initialize
        self.logger.debug("Waiting 30 seconds for network to initialize")
        time.sleep(30)

        # Check if device is registered
        if not self.is_registered:
            self.logger.debug("Device not registered, registering device")
            registration.register()

            # Update registation state
            self.is_registered = registration.is_registered()
            self.device_id = registration.device_id()
            self.verification_code = registration.verification_code()
            self.config_topic = "/devices/{}/config".format(self.device_id)
            self.command_topic = "/devices/{}/commands".format(self.device_id)
            self.telemetry_topic = "/devices/{}/events".format(self.device_id)

        # Initialize pubsub client
        self.pubsub.initialize()

        # Transition to disconnected mode on next state machine update
        self.mode = modes.DISCONNECTED
    def reregister(self) -> Tuple[str, int]:
        """Unregisters device by deleting iot registration data. TODO: This needs to go 
        into the event queue, deleting reg data in middle of a registration update 
        creates an unstable state."""
        self.logger.info("Re-registering device")

        # Check network connection
        if not self.network_is_connected:
            return "Unable to re-register, network disconnected", 400

        # Re-register device and update state
        try:
            registration.delete()
            registration.register()
            self.device_id = registration.device_id()
            self.verification_code = registration.verification_code()
        except Exception as e:
            message = "Unable to re-register, unhandled exception: {}".format(type(e))
            self.logger.exception(message)
            self.mode = modes.ERROR
            return message, 500

        # Transition to init mode on next state machine update
        self.mode = modes.INIT

        # Successfully deleted registration data
        return "Successfully re-registered device", 200
    def load_mqtt_config(self) -> None:
        """Loads mqtt config."""
        self.logger.debug("Loading mqtt config")

        # Load settings from environment variables
        try:
            self.project_id = os.environ["GCLOUD_PROJECT"]
            self.cloud_region = os.environ["GCLOUD_REGION"]
            self.registry_id = os.environ["GCLOUD_DEV_REG"]
            self.device_id = registration.device_id()
            self.private_key_filepath = os.environ["IOT_PRIVATE_KEY"]
            self.ca_certs = os.environ["CA_CERTS"]
        except KeyError as e:
            message = "Unable to load pubsub config, key {} is required".format(e)
            self.logger.critical(message)
            raise

        # Initialize client id
        self.client_id = "projects/{}/locations/{}/registries/{}/devices/{}".format(
            self.project_id, self.cloud_region, self.registry_id, self.device_id
        )

        # Initialize config topic
        self.config_topic = "/devices/{}/config".format(self.device_id)

        # Initialize commands topic
        self.command_topic = "/devices/{}/commands/#".format(self.device_id)

        # Initialize event topic
        test_telemetry_topic = os.environ.get("IOT_TEST_TOPIC")
        if test_telemetry_topic is not None:
            self.telemetry_topic = "/devices/{}/{}".format(self.device_id, test_telemetry_topic)
            self.logger.debug("Publishing to test topic: {}".format(self.telemetry_topic))
        else:
            self.telemetry_topic = "/devices/{}/events".format(self.device_id)
    def upload_image(self, file_name: str) -> None:
        self.logger.debug("Uploading binary image")

        if not self.is_initialized:
            self.logger.warning("Tried to publish before client initialized")
            return

        if file_name == None or len(file_name) == 0:
            error_message = "Unable to publish image, file name "
            "`{}` is invalid".format(file_name)
            self.logger.error(error_message)
            raise ValueError(error_message)

        # Get the camera name and image type from the file_name:
        # /Users/rob/yada/yada/2019-05-08-T23-18-31Z_Camera-Top.png
        base = ''
        try:
            base = os.path.basename(file_name) # get just the file from path
            fn1 = base.split("_")  # delimiter between datetime & camera name
            fn2 = fn1[1]           # 'Camera-Top.png'
            fn3 = fn2.split(".")   # delimiter between file and extension
            camera_name = fn3[0]   # 'Camera-Top'
        except:
            camera_name = base

        device_id = registration.device_id()
        upload_file_name = '{}_{}'.format(device_id, base)

        # commented URL below is for running the firebase cloud function 
        # service locally for testing
        #URL = 'http://*****:*****@{};filename={}'.format(file_name, upload_file_name)

        try:
            # Use curl to do a multi part form post of the binary data (fast) to
            # our firebase cloud function that puts the image in the GCP 
            # storage bucket.
            res = subprocess.run(['curl', '--silent', URL, '-F', DATA])
            self.logger.debug("Uploaded file: {}".format(upload_file_name))

            # Publish a message indicating that we uploaded the image to the
            # public bucket written by the firebase cloud function, and we need
            # to move the image to the usual images bucket we have been using.
            message = {
                "messageType": IMAGE_MESSAGE,
                "varName": camera_name,
                "fileName": upload_file_name,
            }

            message_json = json.dumps(message)
            self.client.publish(self.telemetry_topic, message_json, qos=1)

        except Exception as e:
            error_message = "Unable to publish binary image, unhandled "
            "exception: {}".format(type(e))
            self.logger.exception(error_message)
    def __init__(self, state: State, recipe: RecipeManager) -> None:
        """Initializes iot manager."""

        # Initialize parent class
        super().__init__()

        # Initialize parameters
        self.state = state
        self.recipe = recipe

        # Initialize logger
        self.logger = logger.Logger("IotManager", "iot")
        self.logger.debug("Initializing manager")

        # Initialize our state variables
        self.received_message_count = 0
        self.published_message_count = 0

        # Initialize device info
        self.device_id = registration.device_id()

        # Initialize topics
        self.config_topic = "/devices/{}/config".format(self.device_id)
        self.command_topic = "/devices/{}/commands".format(self.device_id)
        self.telemetry_topic = "/devices/{}/events".format(self.device_id)

        # Initialize pubsub handler
        self.pubsub = PubSub(
            ref_self=self,
            on_connect=on_connect,
            on_disconnect=on_disconnect,
            on_publish=on_publish,
            on_message=on_message,
            on_subscribe=on_subscribe,
            on_log=on_log,
        )

        # Initialize state machine transitions
        self.transitions: Dict[str, List[str]] = {
            modes.INIT: [
                modes.CONNECTED,
                modes.DISCONNECTED,
                modes.ERROR,
                modes.SHUTDOWN,
            ],
            modes.CONNECTED: [
                modes.INIT,
                modes.DISCONNECTED,
                modes.ERROR,
                modes.SHUTDOWN,
            ],
            modes.DISCONNECTED: [
                modes.INIT,
                modes.CONNECTED,
                modes.SHUTDOWN,
                modes.ERROR,
            ],
            modes.ERROR: [modes.SHUTDOWN],
        }

        # Initialize state machine mode
        self.mode = modes.INIT

        # Initialize recipe modes
        self.previous_recipe_mode = recipe_modes.NORECIPE