"Function takes 0 positional arguments" - but it should take self

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Dylanus
Posts: 4
Joined: Mon Dec 31, 2018 7:30 am

"Function takes 0 positional arguments" - but it should take self

Post by Dylanus » Mon Dec 31, 2018 7:54 am

Hi all,

Really sorry about the lack of code tags. I have them turned on in my account settings, but they're off for this post, and I can't see a button to change that. I saw a note in the forum documentation saying an administrator has to enable this. If there's a way for me to turn them on, let me know and I'll fix it.
I hope my subject was descriptive enough. I am getting an error when calling a method in a class I made. The method has been defined as read_temp(self):
but it still seems to think it should not take any arguments.
My source code is this:

Code: Select all

import time
import math
import pyb
from pyb import UART


class tempSensor():
    """Thermistor object takes IO pins for power and signal and                                          
    nominal resistance of parallel resistor. Methods allow for reading                                   
    the temperature. Main 'public' method will return float."""


    def __init__(self, sensor_pin, power_pin, resistance):
        """Pin args are strings corresponding to pyb names, resistance is int"""
        self.sensor_pin = pyb.ADC(sensor_pin)
        self.power_pin = pyb.Pin(power_pin, pyb.Pin.OUT_PP)
        self.resistance = resistance

        self.thermistor_nominal = 10000
        self.temperature_nominal = 25
        self.bcoefficient = 3950
        self.numsamples = 5


    def _read_sensor(self):
        """'Private' method to set power pin up and read value from thermistor.                          
        Power is only on temporarily to avoid heating up thermistor."""
        self.power_pin.high()
        time.sleep(1)
        average = 0
        for i in range(self.numsamples):
            average += self.sensor_pin.read()
        self.power_pin.low()
        return average / self.numsamples


    def read_temp(self):
        """'Public' method to allow user to get temperature value from sensor."""
        reading = self._read_sensor()
        reading = 4095 / reading - 1
        reading = resistance / reading
        temp = reading / self.thermistor_nominal
        temp = math.log(temp)
        temp /= self.bcoefficient
        temp += 1.0 / (self.temperature_nominal + 273.15)
        temp = 1.0 / temp
        temp -= 273.15
        round(temp, 1) # Accuracy will be lost beyond one floating point                                 
        return temp

def esp_transmit(uart, reading):
    uart.write("AT+CIPSEND={}\r\n".format(len(reading)))
    time.sleep(5)
    uart.write(str(reading))


def sensor_test():
    uart = UART(4, 115200)
    uart.init(115200, bits=8, parity=None, stop=1)
    uart.write('AT+CIPSTART="UDP","192.168.0.19",6789\r\n')
    time.sleep(5)

    sensor_left_power = 'Y3'
    sensor_left_read = 'Y12'
    sensor_right_power = 'Y1'
    sensor_right_read = 'Y11'
    resistance = 10000

    sensor_left = tempSensor(sensor_left_read, sensor_left_power, resistance)
    sensor_right = tempSensor(sensor_right_read, sensor_right_power, resistance)

    while True:
        esp_transmit(uart, sensor_left.read_temp())
        time.sleep(5)
        esp_transmit(uart, str(sensor_right.read_temp()))
        time.sleep(5)


sensor_test()
The last part of the code, sensor_test() and esp_transmit(), is a means for me to test the temperature reading. If I hardcode a float into the esp_transmit call and have all this sending to a UDP server on my laptop, it works fine, but if I make the call to sensor.read_temp(), I don't see anything showing up at the server end.
In the REPL, when I import this source file, and create a sensor object, as soon as I call read_temp(), I get
TypeError: read_temp() takes 0 positional arguments but 1 were given
The same goes if I call _read_sensor() directly.
This error normally occurs if self has not been used as a parameter when defining the method. That is the only case I have seen online.
I did also see that MicroPython has a problem with handling arguments sometimes: https://docs.micropython.org/en/latest/ ... #functions
It made me think that there could be some issue with how micropython is handling my code.
When I define all this code in the REPL myself, however, it works without a problem. I even got it to return some values from reading the sensors. So somehow it has an issue with this module.
By the way, I'm using a pyboard, V1.1. tempobjects.py, the name of this module, is in the main directory for the board. boot.py contains the instruction, pyb.main('tempobjects.py')
I'm all out of ideas. Is there something I'm missing about the way MicroPython handles classes?
Thanks for your help.

User avatar
pythoncoder
Posts: 3241
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: "Function takes 0 positional arguments" - but it should take self

Post by pythoncoder » Mon Dec 31, 2018 8:18 am

I can't see anything obviously wrong with your code. It would help if you could post the details of your REPL session from a soft reboot to your error so we can see exactly how it occurs. In general MicroPython handles args and self exactly as per CPython.
Peter Hinch

User avatar
Roberthh
Posts: 1242
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: "Function takes 0 positional arguments" - but it should take self

Post by Roberthh » Mon Dec 31, 2018 8:37 am

When trying to run that on my pyboard, I get several Python error:

in read_temp()
Line 41: resistance is not known.
It should be self.resistance
------------
in esp_transmit()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "sensor.py", line 79, in <module>
File "sensor.py", line 74, in sensor_test
File "sensor.py", line 52, in esp_transmit
TypeError: object of type 'float' has no len()
-------------------------
Obviously, python is right. You should convert reading to a str first and then deal with it.

Dylanus
Posts: 4
Joined: Mon Dec 31, 2018 7:30 am

Re: "Function takes 0 positional arguments" - but it should take self

Post by Dylanus » Mon Dec 31, 2018 11:27 am

Thanks for your replies. I actually got the same errors when I tried again shortly after posting. As soon as I fixed the self.resistance problem everything was fine.
I'm not sure why those issues didn't show up the first time. I'm also not sure why the positional argument error showed up. Once I fixed the variable issue, that problem never showed up again. My code is working perfectly now, but if anyone knows what might cause this, I'd be curious to know.
Is it just that MicroPython has so little space allocated for handling errors that it does strange things like this?

User avatar
dhylands
Posts: 3064
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: "Function takes 0 positional arguments" - but it should take self

Post by dhylands » Mon Dec 31, 2018 6:21 pm

Is it just that MicroPython has so little space allocated for handling errors that it does strange things like this?
Not that I've run into.

However, if you're using Mass Storage to copy files to your pyboard, then I've experienced all types of corruption issues which may explain what you're seeing.

I also noticed that you have a line:

Code: Select all

round(temp,1)
This is essentially a no-op since you aren't assigning the result anyplace. After fixing the self.resistance problem, I'm still getting the same error that @roberthh is getting, which suggests that the code you're running isn't actually the same as the code you posted. I see that your second call to read_temp uses str(sensor_right.read_temp()) where the first call doesn't.

I also noticed in esp_transmit that the first uart.write format(len(reading)) and the second one uses str(reading). The call to str in the second call is presumabloy superfluousness since the reading variable needs to already be a string.

Dylanus
Posts: 4
Joined: Mon Dec 31, 2018 7:30 am

Re: "Function takes 0 positional arguments" - but it should take self

Post by Dylanus » Mon Dec 31, 2018 10:25 pm

Thanks for the code review!
I fixed the string issue last night, but forgot to mention it.
I wasn't too concerned about the sensor_test() function, as it was meant to be temporary, but I've changed esp_transmit to convert input to string before it does anything else, so that everything is consistent now.

Code: Select all

import time
import math
import pyb
from pyb import UART


class tempSensor():
    """Thermistor object takes IO pins for power and signal and
    nominal resistance of parallel resistor. Methods allow for reading
    the temperature. Main 'public' method will return float."""


    def __init__(self, sensor_pin, power_pin, resistance):
        """Pin args are strings corresponding to pyb names, resistance is int"""
        self.sensor_pin = pyb.ADC(sensor_pin)
        self.power_pin = pyb.Pin(power_pin, pyb.Pin.OUT_PP)
        self.resistance = resistance

        self.thermistor_nominal = 10000
        self.temperature_nominal = 25
        self.bcoefficient = 3950
        self.numsamples = 5


    def _read_sensor(self):
        """'Private' method to set power pin up and read value from thermistor.
        Power is only on temporarily to avoid heating up thermistor."""
        self.power_pin.high()
        time.sleep(1)
        average = 0
        for i in range(self.numsamples):
            average += self.sensor_pin.read()
        self.power_pin.low()
        return average / self.numsamples

    
    def read_temp(self):
        """'Public' method to allow user to get temperature value from sensor."""
        reading = self._read_sensor()
        reading = 4095 / reading - 1
        reading = self.resistance / reading
        temp = reading / self.thermistor_nominal
        temp = math.log(temp)
        temp /= self.bcoefficient
        temp += 1.0 / (self.temperature_nominal + 273.15)
        temp = 1.0 / temp
        temp -= 273.15
        temp = round(temp, 1) # Accuracy will be lost beyond one floating point
        return temp


def esp_transmit(uart, reading):
    reading = str(reading)
    uart.write("AT+CIPSEND={}\r\n".format(len(reading)))
    time.sleep(5)
    uart.write(reading)
    

def sensor_test():
    uart = UART(4, 115200)
    uart.init(115200, bits=8, parity=None, stop=1)
    uart.write('AT+CIPSTART="UDP","192.168.0.19",6789\r\n')
    time.sleep(5)

    sensor_left_power = 'Y3'
    sensor_left_read = 'Y12'
    sensor_right_power = 'Y1'
    sensor_right_read = 'Y11'
    resistance = 10000

    sensor_left = tempSensor(sensor_left_read, sensor_left_power, resistance)
    sensor_right = tempSensor(sensor_right_read, sensor_right_power, resistance)

    while True:
        esp_transmit(uart, sensor_left.read_temp())
        time.sleep(5)
        esp_transmit(uart, sensor_right.read_temp())
        time.sleep(5)


sensor_test()
I can now run this code without errors and receive the transmissions on the UDP server. I'm not getting the right readings, as this is code that was written for Arduino. I still have to figure out how to read an NTC thermistor with 3.3V input. Pretty sure it's not 43C in my room!

Post Reply