-1

I am running into a seemingly bizarre problem that I can't figure out - I am trying to create a single object, however for some reason, two are always made.

The two files control a robot i am building.

App.py
  • A flask server which receives in coming commands through a socket.io connection with clients
Hypervisor.py
  • Robot Controller, takes in user commands, sensor data, current agenda, and feeds commands to the MotionController.

I am trying to create a single instance of a the Hypervisor class from within the Flask server app.py, however two are consistently created.

Below is my code and console output showing the double object creation. Why is this happeneing!!?!?!?

App.py

#!/usr/bin/env python
from flask import Flask,  render_template,  session,  request,  send_from_directory,  send_file
from flask_socketio import SocketIO,  emit,  join_room,  leave_room,  close_room,  rooms,  disconnect
import time
import json
import datetime
import logging
import platform
from bColors import bcolors
from RobotSystem.Hypervisor import Hypervisor
from RobotSystem.Services.Utilities.RobotUtils import RobotUtils

async_mode = None
app = Flask(__name__,  static_url_path='/static')
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app,  async_mode=async_mode)
log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR)

thread = None
connections = 0

@app.route('/',  methods=['GET',  'POST'])
def index():
    return render_template('index.html',  async_mode=socketio.async_mode)

def background_thread():
    while True:
        socketio.sleep(1)

@socketio.on('valueUpdate')
def valueUpdateHandler(message):
    RobotUtils.ColorPrinter("app.py",'Value update fired ', 'OKBLUE')
    quadbot.inputData(message)
    data = {}
    data['Recieved'] = True
    return json.dumps(data)

@socketio.on('connect')
def test_connect():
    global connections
    connections+=1
    print_str = "Client connected. "+ str(connections)+  " current connections"
    RobotUtils.ColorPrinter("app.py",print_str, 'OKBLUE')

    global thread, quadbotThread
    if thread is None:
        print "init"
        thread = socketio.start_background_task(target=background_thread)

@socketio.on('disconnect')
def test_disconnect():
    global connections
    connections -= 1
    RobotUtils.ColorPrinter("app.py",str( 'Client disconnected. ' +str(connections)+ " current connections" ), 'OKBLUE')

if __name__ == '__main__':
    global quadbot
    quadbot = Hypervisor()
    socketio.run(app,  debug=True)

Hypervisor.py

#!/usr/bin/python
from Services import *
import time
import math
import json
import sys
import threading
import os
from Queue import Queue,Empty

class Hypervisor():

    def __init__(self):

        if RobotUtils.LIVE_TESTING:
            self.pwm = PWM()
            self.pwm.setPWMFreq(RobotUtils.FREQUENCY)
        else:
            self.pwm = None

        self.inputQueue = Queue()
        self.agendaThread = threading.Thread(group=None,target=self.updateAgendaLoop,name="agendaThread")
        self.agendaThread.start()

        self.data_file_name = RobotUtils.DATA_FILE

        self.front_left = None
        self.front_right = None
        self.back_left = None
        self.back_right = None

        self.TURN_LEFT = RobotUtils.TURN_LEFT
        self.TURN_RIGHT = RobotUtils.TURN_RIGHT
        self.FORWARD = RobotUtils.FORWARD
        self.BACKWARD = RobotUtils.BACKWARD
        self.STOP = RobotUtils.STOP
        self.AUTONOMOUS = RobotUtils.AUTONOMOUS
        self.INVALID_DATA_ERROR = RobotUtils.INVALID_DATA_ERROR

        self.horizVidMotor = Motor(50, RobotUtils.HORIZONTAL_VID_PIN, RobotUtils.HORIZONTAL_VID_MIN_VAL, RobotUtils.HORIZONTAL_VID_MAX_VAL, 0, "horizontal video motor", self.pwm)
        self.vertVidMotor = Motor( 50, RobotUtils.VERTICAL_VID_PIN, RobotUtils.VERTICAL_VID_MIN_VAL, RobotUtils.VERTICAL_VID_MAX_VAL, 0, "vertical video motor", self.pwm)

        self.setup()

        self.motors = [self.front_left, self.front_right,self.back_left,self.back_right, self.horizVidMotor, self.vertVidMotor ]

        self.MotionController = MotionController(self.TURN_LEFT,  self.TURN_RIGHT, self.FORWARD, self.BACKWARD, self.STOP,self.AUTONOMOUS,self.INVALID_DATA_ERROR,
        self.motors, RobotUtils
                                                )
        self.stand()
        RobotUtils.ColorPrinter(self.__class__.__name__, '__init__() finished. Robot Created with id ' +str(id(self)), 'OKBLUE')

    # loads json data and creates Leg objects with add_leg()
    def setup(self):

        with open(self.data_file_name) as data_file:
            data = json.load(data_file)
            constants = data["constants"]
            for i in range(len(data["legs"])):
                self.add_leg(data["legs"][i],constants)

    # reads dictuanary values from input, creates a Leg object, and adds it to leg variables
    def add_leg(self,legData,constants):

        leg_name = legData["name"]

        body_pin                = legData["motors"]["body"]["pinValue"]
        body_offset             = legData["motors"]["body"]["offset"]
        body_center             = constants["bodyCenterValue"] + body_offset
        body_min                = constants["bodyRange"]["min"]
        body_max                = constants["bodyRange"]["max"]

        mid_horiz_value         = legData["motors"]["middle"]["horizValue"]
        middle_pin              = legData["motors"]["middle"]["pinValue"]
        middle_min              = constants["middleRange"]["min"]
        middle_max              = constants["middleRange"]["max"]
        middle_offset_to_center = constants["midOffsetFromHoriz"]

        leg_horiz_value         = legData["motors"]["leg"]["horizValue"]
        leg_pin                 = legData["motors"]["leg"]["pinValue"]
        leg_min                 = constants["legRange"]["min"]
        leg_max                 = constants["legRange"]["max"]
        leg_offset_to_center    = constants["legOffsetFromHoriz"]

        leg = Leg( self.pwm, leg_name, body_pin,    body_min,   body_max,   body_center, mid_horiz_value,   middle_pin, middle_min, middle_max, middle_offset_to_center, leg_horiz_value, leg_pin, leg_min, leg_max, leg_offset_to_center)

        if leg_name == "FR":
            self.front_right = leg

        elif leg_name == "FL":
            self.front_left = leg

        elif leg_name == "BL":
            self.back_left = leg

        elif leg_name == "BR":
            self.back_right = leg

        else:
            print "ERROR: LEG CANNOT BE IDENTIFIED"

    # Called by server when a change in user data is detected
    def inputData(self,data):
        self.inputQueue.put(data)

    def updateAgendaLoop(self):
        while True:
            try:
                data = self.inputQueue.get_nowait()
                self.updateAgenda(data)
            except Empty:
                pass

            time.sleep(RobotUtils.AGENDA_UPDATE_SPEED)
        print '\033[94m' + "Robot: QUEUE READING FINISHED" + '\033[0m'
        sys.exit()

    # acts as central coordinator for the robot - raeads incoming data + state of the bot and calls methods accordingly
    def updateAgenda(self,data):
        self.MotionController.updateCameras(data)
        nextMove = self.MotionController.NextMove(data)
        if nextMove == self.INVALID_DATA_ERROR:
            print "Fix this"
        else:
            self.MotionController.MakeMove(nextMove)

Console Output

Console output

jstm
  • 420
  • 4
  • 14
  • It's unclear to me why this happens by just looking at the code. I can't run the code because I ma missing files, but try to print the stack trace in the __init__(), like this: https://docs.python.org/2/library/traceback.html#traceback-examples . That will show you who is calling __init__() – excalibur1491 Apr 03 '17 at 23:46

1 Answers1

1

First of all a little soapboxing:

If you provide a SSCCE (Short, Self-Contained, Correct (Compilable), Example) then you are more likely to get a response. Also by trimming the example to the minimum required to reproduce you may very well identify the source of the problem yourself. For example the following would be a SSCCE for your issue:

Required dependancies:
pip install flask
pip install flask-socketio
Code:
import logging
from flask import Flask
from flask_socketio import SocketIO

logging.basicConfig(level=logging.ERROR)
app = Flask(__name__)
socketio = SocketIO(app)

class Hypervisor():
    def __init__(self):
        print('Hypervisor initialized')

if __name__ == '__main__':
    quadbot = Hypervisor()
    socketio.run(app,  debug=True)
Output:
Hypervisor initialized
Hypervisor initialized

Explanation

If you use a debugger the answer presents itself fairly easily. You can use the debugger tools included in your IDE, or you can always use The Python Debugger from the python standard library (a.k.a. pdb).

While it's beyond the scope of this answer to provide a complete tutorial of pdb, the method we will use to debug is to break into the debugger by importing pdb and inserting the following pdb.set_trace() at the point you want to begin debugging.

Since the issue is with Hypervisor creation the logical point to insert the debugger is just before the initialization of Hypervisor like so:

if __name__ == '__main__':
    import pdb
    global quadbot
    pdb.set_trace() # debugging begins here
    quadbot = Hypervisor()
    socketio.run(app, debug=True)

From this point when you run your app, it will drop into pdb right before the initialization of Hypervisor. Before you do so though, you will need to know two commands for using pdb.

The first is n for next which continues execution until to the next line of code in the current function.

The second is s for step which will step into the current code stopping at first opportunity.

Or as the docs explain it:

The difference between next and step is that step stops inside a called function, while next executes called functions at (nearly) full speed, only stopping at the next line in the current function.

So armed with this knowledge run your app, and you will be presented with the pdb prompt which should look something like this:

-> quadbot = Hypervisor()
(Pdb)

The -> shows the current line of code about to be executed. (Pdb) is the prompt awaiting input. At this point let's initialize the Hypervisor by proceeding to the next line of code by typing n and enter. At this point you should see a single Hypervisor has been created.

[4/3/2017 20:02:46 ] Hypervisor:  __init__() finished. Robot Created with id 4218654299

And be returned to the pdb prompt for the next line:

-> socketio.run(app,  debug=True)
(Pdb)

So since there's only one more line of code left to run, the issue is somewhere in socketio.run. So this time we will step into the current line of code by typing s and enter which will bring you to:

-> def run(self, app, host=None, port=None, **kwargs):
(Pdb) 

From this point, keep stepping to the next line of code until you see the second Hypervisor initialization. Looking at the trace you should see something like the following:

-> app.run(host=host, port=port, threaded=True,
(Pdb) n

-> use_reloader=use_reloader, **kwargs)
(Pdb) n

-> quadbot = Hypervisor()
(Pdb) n
[4/3/2017 20:03:52 ] Hypervisor:  __init__() finished. Robot Created with id 4367452293

This shows you that right after the app.run executes (it takes two n's to execute since the command spans two lines of code), it returns to the quadbot = Hypervisor() line of code we started at. So a closer look at app.run shows an argument called use_reloader. If you haven't guessed already, looking at the Flask-SocketIO docs tells us:

use_reloader - True to enable the Flask reloader, False to disable it.

With a little more digging we can find the following two nuggets of wisdom in the Flask docs:

debug
The debug flag. Set this to True to enable debugging of the application. In debug mode the debugger will kick in when an unhandled exception occurs and the integrated server will automatically reload the application if changes in the code are detected.

and

run
...
Flask will suppress any server error with a generic error page unless it is in debug mode. As such to enable just the interactive debugger without the code reloading, you have to invoke run() with debug=True and use_reloader=False. Setting use_debugger to True without being in debug mode won’t catch any exceptions because there won’t be any to catch.

So armed with this information you can choose to either disable debug, or pass the option use_reloader=False to socketio.run.

TL;DR

You most likely want to disable the Flask reloader by passing use_reloader=False to socketio.run

if __name__ == '__main__':
    global quadbot
    quadbot = Hypervisor()
    socketio.run(app, debug=True, use_reloader=False)
Labrys Knossos
  • 393
  • 2
  • 7