CircuitPython Interpreter vs compiler .py vs .mpy

CircuitPython on hardware including Adafruit's boards, and CircuitPython libraries using Blinka on host computers.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
blakebr
 
Posts: 985
Joined: Tue Apr 17, 2012 6:23 pm

CircuitPython Interpreter vs compiler .py vs .mpy

Post by blakebr »

Hello,

A couple questions.

Does CircuitPython do a full compile then execute that code?
Or does it compile a token file and then interpret that file?
Or is it a straight-up interpreter?
Are the comments included in the intermediate or final code?

Why am I asking these questions???
1) I am getting different results when I use the adafruit_irremote.py vs the adafruit_irremote.mpy library.
2) I have gotten different results depending if I include an unused intrinsic library or not.

I want to be able to reliably reproduce my results before I ask more questions or provide examples.

Bruce

User avatar
dastels
 
Posts: 15819
Joined: Tue Oct 20, 2015 3:22 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by dastels »

CircuitPython compiles to bytecode, which is what the MPY files are. They are simply bytecompiled ahead of time, saving the time and ram required at runtime (and in the constrained environment of the MCU).

Comments are skipped when the python source is tokenized. I.e. they are not present in the bytecompiled code.

What sort of different results?

Dave

User avatar
blakebr
 
Posts: 985
Joined: Tue Apr 17, 2012 6:23 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by blakebr »

Dave,

The short answer: Send an IR code for SKIP30 via the encode routine to adafruit_irremote…. With .mpy I get the SKIP30 codes sent by IR modulation. With .py I get a consistent undefined code. I can get you the exact codes tomorrow.

This does not make sense at all. The .py routine should give the same results as the .mpy with the same inputs.

Are the imports compiled into the executable code or are they appended/prepended?

Bruce

User avatar
dastels
 
Posts: 15819
Joined: Tue Oct 20, 2015 3:22 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by dastels »

No executable code. Imported MPY files just get loaded. PY files get byte-compiled when it's loaded and the byte code is stored in ram (in both cases).

That is odd. they should be identical once loaded.

Dave

User avatar
blakebr
 
Posts: 985
Joined: Tue Apr 17, 2012 6:23 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by blakebr »

Dave,

That is what I thought. I replaced the transistor, the transmit IR LED, the jumper wire, and the resistors. My last things was to replace the .py files with .mpy files. That is when it worked. The only examples are ones showing receiving IR codes. None transmitting codes. This could be why.

I moved from 6.3.0 .mpy to .py when I was moving from 6.3.0 to 7.0.0. Now I will move to the 7.0.0 .mpy.

Bruce

User avatar
blakebr
 
Posts: 985
Joined: Tue Apr 17, 2012 6:23 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by blakebr »

David,

OK, I have tested this in every way I can think of:

Raspberry Pi Pico RP2040:
Using .mpy works fine with import microcontroller
Using .mpy works fine without import microcontroller
Using .py works fine with import microcontroller
Using .py works fine without import microcontroller

AdaFruit QY Py RP2040:
Using .mpy works fine with import microcontroller
Using .mpy works fine without import microcontroller
Using .py works fine with import microcontroller
Using .py works fine without import microcontroller

AdaFruit Feather RP2040:
Not built yet.

AdaFruit ItsyBitsy RP2040:
Using .mpy works fine with import microcontroller
Using .mpy works fine without import microcontroller
Using .py works fine without import microcontroller
Using .py BAD with import microcontroller ##########################
During previous troubleshooting microcontroller had to be imported to make it work. {shrug}

After 4 calls to transmit the SKIP30 codes the device stops. No error messages. Ctrl-C does not work. Ctrl-D does not work. RESET Button gets control back. Removing power gets control back. If I let it set about 45 seconds it disconnects and reconnects to my Windows 10 machine and the alert box says "USB device not recognized". Push RESET and we are back.

Transmitting the wrong code from the ItsyBitsy has not come back with the .py libraries after testing with the ,mpy libraries. Confused!

When I am using the .mpy lib I am using the entire library; except on the Raspberry Pi Pico, it has only the three listed next. The .py lib only has adafruit_esp32spi, adafruit_irremote, and neopixel.

Bruce

Code: Select all

'''
    IR Relay and translate by B.B.Blake 10/10/2021
'''
#################### Import entrincic libraries ####################
import array
import board
import digitalio
from   digitalio import DigitalInOut, Direction, Pull
import neopixel
import pulseio
import pwmio
import time
import microcontroller # I don't know why we need it, but we do.
#################### Import extrincic libraries ####################
import adafruit_esp32spi
from   adafruit_esp32spi import PWMOut
import adafruit_irremote

#################### Figure out what board we have ####################
Pico, Itsy, QTPy, Fthr, ANRC = False, False, False, False, False
bd    = board.board_id # Ask the AdaFruit OS what board we are using
print("\f" + bd, end=" - ")
if  (bd == "raspberry_pi_pico"):         Pico = True
elif(bd == "adafruit_itsybitsy_rp2040"): Itsy = True
elif(bd == "adafruit_qtpy_rp2040"):      QTPy = True
elif(bd == "adafruit_feather_rp2040"):   Fthr = True
elif(bd == "arduino_nano_rp2040_connect_rp2040"): ANRC = True
else:
    while(True):
        print("Board Not Supported.")
        time.sleep(15)
        pass
print("Board Supported.")

#################### Set some variables and constants ####################
freq  = 38000 # IR Tone Frequency
duty  = 32768 # IR Tone Duty Cycle, aka 50%

RED     = (255, 0, 0)
GREEN   = (0, 255, 0)
BLUE    = (0, 0, 255)
YELLOW  = (255,255,0)
CYAN    = (0,255,255)
MAGENTA = (255,0,255)
WHITE   = (255, 255, 255)
BLACK   = (0, 0, 0)

AdaFruit = False
# IR Codes from TiVo IR transmitter, the Peanut
SKIP30  = [0xD0, 0x27, 0x30, 0x85] # SKIP30 - TiVo
INPUT   = [0xF4, 0x0B, 0xFB, 0x04] # INPUT - RCA Source Select
# IR Codes from eBay IR transmitter
Skip_1  = [0xBA, 0x45, 0xFF, 0x00]
Skip_2  = [0xB9, 0x46, 0xFF, 0x00]
Skip_3  = [0xB8, 0x47, 0xFF, 0x00]
Skip_4  = [0xBB, 0x44, 0xFF, 0x00]
Skip_5  = [0xBF, 0x40, 0xFF, 0x00]
Skip_6  = [0xBC, 0x43, 0xFF, 0x00]
Skip_7  = [0xF8, 0x07, 0xFF, 0x00]
Skip_8  = [0xEA, 0x15, 0xFF, 0x00]
Skip_9  = [0xF6, 0x09, 0xFF, 0x00]
Skip_10 = [0xE6, 0x19, 0xFF, 0x00]
OK      = [0xE3, 0x1C, 0xFF, 0x00]
# IR Codes from AdaFruit IR transmitter
if(AdaFruit):
    Skip_1  = [0xEF, 0x10, 0xBF, 0x00]
    Skip_2  = [0xEE, 0x11, 0xBF, 0x00]
    Skip_3  = [0xED, 0x12, 0xBF, 0x00]
    Skip_4  = [0xEB, 0x14, 0xBF, 0x00]
    Skip_5  = [0xEA, 0x15, 0xBF, 0x00]
    Skip_6  = [0xE9, 0x16, 0xBF, 0x00]
    Skip_7  = [0xE7, 0x18, 0xBF, 0x00]
    Skip_8  = [0xE6, 0x19, 0xBF, 0x00]
    Skip_9  = [0xE5, 0x1A, 0xBF, 0x00]
    Skip_10 = [0xF3, 0x0C, 0xBF, 0x00]
    OK      = [0xF6, 0x09, 0xBF, 0x00] # aka ENTER/SAVE

#################### Configure things based on the board found ####################
if(Pico == True):
    IR_TX            = board.GP26

    IR_RX            = board.GP2
    IR_PWR           = board.GP1
    IR_PWR           = DigitalInOut(IR_PWR)
    IR_PWR.direction = Direction.OUTPUT
    IR_PWR.value     = True


    R_LED            = DigitalInOut(board.GP6)
    R_LED.direction  = Direction.OUTPUT
    B_LED            = DigitalInOut(board.GP7)
    B_LED.direction  = Direction.OUTPUT
    G_LED            = DigitalInOut(board.GP8)
    G_LED.direction  = Direction.OUTPUT
    R_LED.value, B_LED.value, G_LED.value = True,  True,  True
    time.sleep(0.5)
    R_LED.value, B_LED.value, G_LED.value = False, True, False 

if(Itsy == True):
    IR_TX            = board.TX

    IR_RX            = board.RX  # Pin connected to  IR receiver.
    IR_PWR           = board.D5
    IR_PWR           = DigitalInOut(IR_PWR)
    IR_PWR.direction = Direction.OUTPUT
    IR_PWR.value     = True

    PIXEL_PIN0       = board.NEOPIXEL # NeoPixel #0
    PIXELS_NUM0      = 1              # NeoPixel #0
    PIXEL_BRIGHT0    = 0.25           # NeoPixel #0
    PIXEL_ORDER0     = neopixel.GRB   # NeoPixel #0
    status_light0    = neopixel.NeoPixel(PIXEL_PIN0, PIXELS_NUM0, brightness=PIXEL_BRIGHT0, auto_write=True,  pixel_order=PIXEL_ORDER0)
    status_light0[0] = WHITE
    PIXEL_PIN1       = board.D7       # NeoPixel #1
    PIXELS_NUM1      = 1              # NeoPixel #1
    PIXEL_BRIGHT1    = 0.25           # NeoPixel #1
    PIXEL_ORDER1     = neopixel.RGB   # NeoPixel #1  (In, +5, Gnd, Out]-flat side to Right [RGB]
    status_light1    = neopixel.NeoPixel(PIXEL_PIN1, PIXELS_NUM1, brightness=PIXEL_BRIGHT1, auto_write=True,  pixel_order=PIXEL_ORDER1)
    status_light1[0] = WHITE
    time.sleep(0.5)
    status_light0[0] = BLUE
    status_light1[0] = BLUE

if(QTPy):

#  Use SDA to provide +3.3 to Receiver, Use SCK to provide GND to Receiver
    IR_TX            = board.TX

    IR_RX            = board.RX

    PIXEL_PIN0       = board.NEOPIXEL # NeoPixel #0
    PIXELS_NUM0      = 1              # NeoPixel #0
    PIXEL_BRIGHT0    = 0.25           # NeoPixel #0
    PIXEL_ORDER0     = neopixel.GRB   # NeoPixel #0
    status_light0    = neopixel.NeoPixel(PIXEL_PIN0, PIXELS_NUM0, brightness=PIXEL_BRIGHT0, auto_write=True,  pixel_order=PIXEL_ORDER0)
    status_light0[0] = WHITE 
    time.sleep(0.5)
    status_light0[0] = BLUE

if(Fthr):
    pass

if not QTPy: LED              = DigitalInOut(board.LED)
if not QTPy: LED.direction    = Direction.OUTPUT

#################### Create IR encoder and decoder ####################
# Create pulse INPUT and IR decoder.
pulses  = pulseio.PulseIn(IR_RX, maxlen=200, idle_state=True)
#pulseout = pulseio.PulseOut(IR_TX, frequency=freq, duty_cycle=duty) Duplicate
decoder = adafruit_irremote.GenericDecode()

# Create a 'pwmio' OUTPUT, to send infrared signals on the IR transmitter @ 38KHz
pwm = pwmio.PWMOut(IR_TX, frequency=freq, duty_cycle=duty) 
pulseout = pulseio.PulseOut(pwm)
# Create an encoder that will take numbers and turn them into NEC IR pulses
encoder = adafruit_irremote.GenericTransmit(header=[9500, 4500], zero=[562,  562], one=[562, 1687], trail=562, debug=False) 
#encoder = adafruit_irremote.GenericTransmit((9500, 4500), (562, 1687), (562, 562), 562)
print("#"*80)

#################### Function Definitions ####################
# Fuzzy pulse comparison function:
def fuzzy_pulse_compare(pulse1, pulse2, fuzzyness=0.2):
    if(Pico): R_LED.value, G_LED.value, B_LED.value = False, False, False
    if(Itsy): status_light0.fill(BLACK)
    if(Itsy): status_light1.fill(BLACK)
    if(QTPy): status_light0.fill(BLACK)
#    print(len(pulse1), len(pulse2))
    if len(pulse1) != len(pulse2):
#        print("Pulse Lengths don't match.")
        if(Pico): B_LED.value = True
        if(Itsy): status_light0.fill(BLUE)
        if(Itsy): status_light1.fill(BLUE)
        if(QTPy): status_light0.fill(BLUE)
        return False
    for i in range(len(pulse1)):
        threshold = (int(pulse1[i]) * fuzzyness)
        if abs(pulse1[i] - pulse2[i]) > threshold:
#            print(abs(pulse1[i] - pulse2[i]), i)
            if(Pico): R_LED.value = True
            if(Itsy): status_light0.fill(RED)
            if(Itsy): status_light1.fill(RED)
            if(QTPy): status_light0.fill(RED)
            return False
    if(Pico): G_LED.value = True
    if(Itsy): status_light0.fill(GREEN)
    if(Itsy): status_light1.fill(GREEN)
    if(QTPy): status_light0.fill(GREEN)
    return True

def make_fingerprint(datain, nbits=32, debug=False):
    datax = datain.copy()
    header=[9500, 4500]
    zero=[562,  562]
    one=[562, 1687]
    trail=562
    bits_to_send = len(datax) * 8
    if nbits is not None and nbits < bits_to_send:
        bits_to_send = nbits
    durations = array.array("H", [0] * (2 + bits_to_send * 2 + (0 if trail is None else 1)))
    durations[0] = header[0]
    durations[1] = header[1]
    if trail is not None:
        durations[-1] = trail
    out = 2
    bit_count = 0
    for byte_index, _ in enumerate(datax):
        byte_count = byte_index  # bbb
    for j in range(byte_count, -1, -1): # bbb
        for i in range(7, -1, -1): # bbb
            bit = datax[j] & 1 # bbb
            datax[j] = datax[j] >> 1 # bbb
            if(bit): # bbb
                durations[out] = one[0]
                durations[out + 1] = one[1]
            else:
                durations[out] = zero[0]
                durations[out + 1] = zero[1]
            out += 2
            bit_count += 1
            if bit_count >= bits_to_send:
                break
    if debug:
        print(durations)
    return(durations)

def skip_loop(loops):
    time.sleep(1)
    if not QTPy: LED.value = True
    for xx in range(loops):
        print(loops-xx, end=" ")
#        print( SKIP30 ) # [0xD0, 0x27, 0x30, 0x85] # SKIP30
#        IR_Command = [0xD0, 0x27, 0x30, 0x85] # SKIP30
        Ky = [0xD0, 0x27, 0x30, 0x85]
        IR_Command = Ky.copy()
#        print(SKIP30, IR_Command, Ky, 1)
        encoder.transmit(pulseout, reverse_bits(IR_Command), nbits=32)
#        print(SKIP30, IR_Command, Ky, 2)
        time.sleep(0.5)
    print("")
    if not QTPy: LED.value = False

def reverse_bits(IN):
    OUT = [0,0,0,0]
    Ks = IN.copy()
    for y in range(8): # Reverse the bits.
        for x in range(4): # Bytes 1 thru 4
            Bit     = (Ks[x] & 1)
            Ks[x]   = (Ks[x] >> 1)
            OUT[3-x] = ((OUT[3-x] << 1) + Bit)
    return(OUT)

#################### Main program ####################
ion = False
while ion: # Just print what the IR receiver receives
    time.sleep(0.3)
    pulses.clear()
    time.sleep(0.4)
    pulses.resume()
    time.sleep(0.3)
    pulse1 = decoder.read_pulses(pulses)
    print("IR Code Received")
    print(len(pulse1))
    print(pulse1)
    print("#"*80)
while not ion: # Perform actions depending what the IR receiver receives
#   Loop waiting to receive pulses.
    while True:
        if(AdaFruit): IR_ = 'AdaFruit'
        else:         IR_ = 'eBay'
        time.sleep(0.1)
        pulses.clear()
        time.sleep(0.1)
        pulses.resume()
        time.sleep(0.1)
        print('IR listener started...')
        # Wait for an IR Code to be detected.
        detected = decoder.read_pulses(pulses)
#        print(detected)
        print('got an IR Code...')
        # Got an IR Code, now compare.
        if fuzzy_pulse_compare(make_fingerprint(INPUT), detected, 0.5):
            print('Received correct INPUT TiVo/RCA remote control press!')
            print('Send Command to turn off the Source Select menu on the TV screen.')
            time.sleep(0.5)
            IR_Command = [0xF4, 0x0B, 0xFB, 0x04] # INPUT - Clear TV Screen
            encoder.transmit(pulseout, reverse_bits(IR_Command), nbits=32)
            skip_loop(7)
        elif fuzzy_pulse_compare(make_fingerprint(OK), detected, 0.5):
            print('Received correct OK '+IR_+' remote control press!')
            pass
        elif fuzzy_pulse_compare(make_fingerprint(SKIP30), detected, 0.5):
            print('Received correct SKIP30 TiVo remote control press!')
            pass
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_1), detected, 0.5)):
            print('Received Skip_1 '+IR_+' remote control press!')
            skip_loop(1)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_2), detected, 0.5)):
            print('Received Skip_2 '+IR_+' remote control press!')
            skip_loop(2)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_3), detected, 0.5)):
            print('Received Skip_3 '+IR_+' remote control press!')
            skip_loop(3)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_4), detected, 0.5)):
            print('Received Skip_4 '+IR_+' remote control press!')
            skip_loop(4)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_5), detected, 0.5)):
            print('Received Skip_5 '+IR_+' remote control press!')
            skip_loop(5)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_6), detected, 0.5)):
            print('Received Skip_6 '+IR_+' remote control press!')
            skip_loop(6)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_7), detected, 0.5)):
            print('Received Skip_7 '+IR_+' remote control press!')
            skip_loop(7)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_8), detected, 0.5)):
            print('Received Skip_8 '+IR_+' remote control press!')
            skip_loop(8)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_9), detected, 0.5)):
            print('Received Skip_9 '+IR_+' remote control press!')
            skip_loop(9)
        elif(fuzzy_pulse_compare(make_fingerprint(Skip_10), detected, 0.5)):
            print('Received Skip_10 '+IR_+' remote control press!')
            skip_loop(10)
        else:
            print(detected)
            pass
        if(Pico): G_LED.value = False
        if(Itsy): status_light0.fill(BLACK)
        if(Itsy): status_light1.fill(BLACK)
        if(QTPy): status_light0.fill(BLACK)
        time.sleep(0.1)
        pulses.clear()
        time.sleep(0.1)
        pulses.resume()
        time.sleep(0.1)
        print("#"*80)

User avatar
blakebr
 
Posts: 985
Joined: Tue Apr 17, 2012 6:23 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by blakebr »

Dave,

If you want to see what happens.
Change Line 48 to True

Code: Select all

AdaFruit = True
Get - AdaFruit Mini Remote Control PRODUCT ID: 389

Bruce

User avatar
dastels
 
Posts: 15819
Joined: Tue Oct 20, 2015 3:22 pm

Re: CircuitPython Interpreter vs compiler .py vs .mpy

Post by dastels »

I heard back: it's a known bug and there's an oipen issue to deal with it: https://github.com/adafruit/circuitpython/issues/5469.

Dave

Locked
Please be positive and constructive with your questions and comments.

Return to “Adafruit CircuitPython”