Week 6. Sep 26.Oct3 2018 - michelle-qin/Portfolio GitHub Wiki

Noticed an error in the Perpetual Motion machine code where the button wouldn't toggle the gate or staircase until the second button press. Worked with mentor Nikolai to figure it out last week by calling the toggle function twice - our theory is the first toggle function is being read too quickly that it's not registering.

To make it more clear, instead of calling toggle twice, we made a new function that we could use instead. See updated code below.

def reliableSetPWM(port, PWM):
    RPiMIB.sendPWM(port, PWM)
    sleep(0.05) 
    RPiMIB.sendPWM(port, PWM)

It is implemented as follows:

def openGate(self, isOpen):
    if (isOpen == False):
        self.ids.gate.text = "Open Gate"
        reliableSetPWM(4, 500)
        #RPiMIB.sendPWM(4, 500)
        print('close gate')
    else:
        self.ids.gate.text = "Close Gate"
        print('open gate')
        reliableSetPWM(4, 4000)
    self.isGateOpen = isOpen

Another error we noticed is that the ramp turning on/off seemed to affect the other components. This might be due to interfering PWM signals. To account for this, we simply changed the order of the initialize() method. See below.

def initialize(self):
    self.isGateOpen = False
    self.isRampHome = HOME
    self.isStaircaseOn = False
    self.resubmitState()

Another function Nikolai and I created is resubmitState() which is supposed to account for the same thing as stated above (interference from ramp).

def resubmitState(self):
    if self.submitRamp:
        self.homeRamp(self.isRampHome)
        self.submitRamp = False
    self.turnOnStaircase(self.isStaircaseOn)
    self.openGate(self.isGateOpen)

New & Strange Error: the ramp was only going up 1/4 of the way when it should've been going to the top of the ramp. Retraced to see if any code was changed - didn't find anything. When the ramp goes to the top, it moves the "rampLength" of 730. I replaced that value with 1500 and noticed the ramp went up further (around 1/2 up the ramp). I increased the value to 3000, but that caused the ramp to crash into the top. Tested different values and then ultimately went back to 730, which worked as desired. Nikolai explained to me that the problem may have occurred because of inconsistency with the hardware (due to cheap material). He gave some wise advice for software engineers: sometimes, we just have to work around the hardware with the code.

Expansion: I want to run the ramp and staircase simultaneously in the Start process. The error right now is interfering PWM signals, which causes the staircase to lag and run way more slowly than it should. I worked with Nikolai, who taught me some important concepts. Essentially, Kivy is sending a signal for the ramp to run, and then it blocks any other user-interface touch (such as, a touch to start the staircase) until the ramp is completely done running. We went into the Stepper.py file (where the ramp.home function is defined) to confirm this. See function below (particularly, the while loop, which acts to block any action until the readSwitch() == True, or when the ramp is finished running).

def home(self, direction):
    self.run(direction, self.speed)
    
    while self.readSwitch() == False:
        continue
        
    self.hardStop()
    self.setAsHome()

Nikolai and I decided to go about this by making a function that would use some parts of the Stepper functions, but would avoid the blocking part. This function would be written in the main.py file. Below is what we have, followed by explanations.

screen = MainScreen(name = 'main')
Clock.schedule_interval(screen.update, 1.0/30.0)
sm.add_widget(screen)

def update(self, timeElapsed):
	#pull ramp state
    print('update')
    if self.readSwitch():
        self.hardStop()
        self.setAsHome()

These functions act to add a timer that will check (or, update) the ramp status in equal intervals. This way, it will remove the blocking because instead of checking the status at the end, it checks the status throughout the process). Once the self.readSwitch() is true, the ramp will stop and the home will be set to the new position.

Another thing I learned today: "self" is only defined in the class that it's in. If you're not in a class, there is no "self". In order to call a function from another class, you have to do "(object name).(method name)". If you're calling the method in the class, it is "(self).(method name)"

Continued working with Nikolai to make the ramp run simultaneously with other functions turning on (i.e., to make the start process more efficiently, we want the ramp to go home AS the staircase turns on and the gate opens). Updated code see below followed by explanations.

We made a new variable called "notRampSubmissionComplete" in the class. This variable helps with deciding when we should stop the ramp by tracking its status (complete/not).

def update(self, timeElapsed):
	#pull ramp state
    print('update')
    if self.notRampSubmissionComplete:
        #readSwitch returns true if the ramp is home 
        if (self.isRampHome == HOME) and ramp.readSwitch():  
            ramp.hardStop()
            ramp.setAsHome()
            self.notRampSubmissionComplete = False
        #in Motor.py a relativeMove is done if the second bit of 
        #getStatus is set
        elif (self.isRampHome == TOP) and \
                (ramp.getStatus() & 0x2) != 0:
            self.notRampSubmissionComplete = False

In the line (if (self.isRampHome == HOME) and ramp.readSwitch():), we are checking 1) if we want the ramp to be home AND 2) if the ramp is at home. If conditions are met, we want to stop the ramp and change the "notRampSubmissionComplete" variable to be false.

In the line (elif (self.isRampHome == TOP) and
(ramp.getStatus() & 0x2) != 0:), we are checking 1) if we want the ramp to be at the top AND 2) if the ramp has reached the top. The second condition took more time to understand and write. Nikolai and I looked into what actually happens when the ramp goes to the top. Our understanding was built as follows (consisted of tracing through different files to see where different functions were defined):

main.py - going to the top, relativeMove is called.

def homeRamp(self, isHome):
    if (isHome == HOME):
        self.ids.ramp.text = "Ramp To Top"
        print('ramp home') 
        ramp.run(0, ramp.speed)
    else:
        self.ids.ramp.text = "Ramp Home"
        # If PB0 is high
        print('ramp at top')
        ramp.relativeMove(rampLength)           
        print('ramp done');              
    self.isRampHome = isHome

Stepper.py - relativeMove is defined here. What is waitMoveFinish()?

def relativeMove(self, distance):
    numberOfSteps = distance * self.microSteps * self.stepsPerUnit
    self.move(int(numberOfSteps))
    self.waitMoveFinish()

Motor.py - waitMoveFinish is defined here. The while loop checks to see if "status" has a 2^1 - if yes, it continues to check status - otherwise, the ramp is at the top and is finished moving.

def waitMoveFinish(self):
    status = 1
    while status:
        status = self.getStatus()
        status = not((status >> 1) & 0b1)

Going back to the function we wrote...

        elif (self.isRampHome == TOP) and \
                (ramp.getStatus() & 0x2) != 0:
            self.notRampSubmissionComplete = False

The second condition basically converts the binary bit to hex. If the status shares 2^0, that means the ramp is at the top and should be stopped. We didn't write any code to actually stop the code (like the previous block that stops the ramp at home) because in the Motor.py file it doesn't seem like "waitMoveFinish" stops the ramp.

(Received an interesting lesson from Nikolai on bits...)

Note to self: Another thing I want to do is implement sensors for "perpetually" running process