User avatar
RogerW
Posts: 286
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Error from pigpio.spi_close()

Tue May 12, 2015 8:52 am

I am tryng to create a class to access an MCP3008 ADC and am getting a strange error when closing the spi handle. I have cut down the code to the following.

Code: Select all

import pigpio as pg

pi = pg.pi()

class ADC():
	def __init__(self):
		self.handle = pi.spi_open(0,50000,0)
		
	def __del__(self):
		pi.spi_close(self.handle)
a = ADC()

#handle = pi.spi_open(0,50000,0)
#pi.spi_close(handle)		

pi.stop()
/code]
The error I get is
[code]
Exception AttributeError: "'NoneType' object has no attribute 'send'" in <bound method ADC.__del__ of <__main__.ADC object at 0xb6965110>> ignored
If I comment out a = ADC() and remove the comments from the two lines below it works. To me the logic appears to be the same.

I think an exception is being thrown when the close call tries to send a message - but why?

Roger Woollett

User avatar
MarkHaysHarris777
Posts: 1820
Joined: Mon Mar 23, 2015 7:39 am
Location: Rochester, MN
Contact: Website

Re: Error from pigpio.spi_close()

Tue May 12, 2015 9:17 am

Where is the rest of the code, and where is the full trace-back?
marcus
:ugeek:

User avatar
joan
Posts: 14680
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 9:30 am

It looks like the sort of error you might get if the pigpio daemon isn't running or (more likely) hasn't been instantiated, e.g. perhaps a missing pi = pigpio.pi().

The pi.stop() closes the connection.

If the __del__ method is called as a destructor there will be no pi object to send to.

User avatar
paddyg
Posts: 2464
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 9:52 am

As @joan says when your program drops off the end then ADC.__del__() will be forced to run and at that point you've killed off the pg.pi() object by previously calling pi.stop().

You might try doing a = None before pi.stop() but the python docs explain that __del__ methods aren't really destructors and only get called when the garbage collector thinks it's necessary so you might still get the error.

However the error is happening after the program has finished so it's really neither here nor there.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

User avatar
joan
Posts: 14680
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 9:56 am

paddyg wrote: ...
However the error is happening after the program has finished so it's really neither here nor there.
I tend to add a specific close method to a class and explicitly call that method if any termination is needed.

I'd certainly do that in the OP's case as otherwise the handle will not be freed and will be left dangling in the daemon.

User avatar
RogerW
Posts: 286
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 10:02 am

I think you are right Paddy. The pi object appears before the ADC one and will probably be destroyed first. I think I need to find a way to keep a reference to it in the ADC object so it cannot be destroyed first. Unfortunately python does not have pointers or references - maybe I can manipulate the reference count in the pi object.
Thanks for you help.

Roger Woollett

User avatar
MarkHaysHarris777
Posts: 1820
Joined: Mon Mar 23, 2015 7:39 am
Location: Rochester, MN
Contact: Website

Re: Error from pigpio.spi_close()

Tue May 12, 2015 10:10 am

RogerW wrote:Unfortunately python does not have pointers or references - maybe I can manipulate the reference count in the pi object.
A a matter of fact, python does have object references... some people confuse them with variables. An object reference count is maintained that prevents garbage collection from cleaning up while there is at least one reference count on the object. You may use Py_INCREF() and Py_DECREF() to manipulate an object's reference count. See the python docs.
marcus
:ugeek:

User avatar
RogerW
Posts: 286
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 10:18 am

MarkHaysHarris777 wrote:
RogerW wrote:Unfortunately python does not have pointers or references - maybe I can manipulate the reference count in the pi object.
A a matter of fact, python does have object references... some people confuse them with variables. An object reference count is maintained that prevents garbage collection from cleaning up while there is at least one reference count on the object. You may use Py_INCREF() and Py_DECREF() to manipulate an object's reference count. See the python docs.
As I understand it these are C functions. If possible I would like to stay in python.

Roger Woollett

User avatar
paddyg
Posts: 2464
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 10:27 am

Roger, could you not explicitly do what you have in the __del__ method? (Bearing in mind the warning by @joan that you *do* need to do this) Along the lines of:

Code: Select all

import pigpio as pg

pi = pg.pi()

class ADC():
   def __init__(self):
      self.handle = pi.spi_open(0,50000,0)
      
   def spi_close(self):
      pi.spi_close(self.handle)

a = ADC()

a.spi_close()      

pi.stop()
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

User avatar
RogerW
Posts: 286
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 10:46 am

paddyg wrote:Roger, could you not explicitly do what you have in the __del__ method? (Bearing in mind the warning by @joan that you *do* need to do this) Along the lines of:

Code: Select all

import pigpio as pg

pi = pg.pi()

class ADC():
   def __init__(self):
      self.handle = pi.spi_open(0,50000,0)
      
   def spi_close(self):
      pi.spi_close(self.handle)

a = ADC()

a.spi_close()      

pi.stop()
I could do that but I am trying to avoid it. In the "full works" I have a base class which creates the pi object and has an explicit refernce count. when the reference count gets back to zero pi.stop() is called. This way a number of objects can share a single pi object and tidy-up operations like close() are all done automatically.

Roger Woollett

User avatar
paddyg
Posts: 2464
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 11:33 am

hard to know without seeing the actual code but could whatever method increments and decrements the reference count actually add the ADC object to a set and the decrement job involves calling spi_close() explicitly? Did you try setting a = None? Though not guaranteed to work it often does (might need a short sleep before pi.stop())
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

User avatar
RogerW
Posts: 286
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Re: Error from pigpio.spi_close()

Tue May 12, 2015 12:43 pm

I think I am going to have to admit defeat for the moment and require all pigpio based classes to implement a close(). I think I can provide a base class __del__ which checks for closure and throws an exeption if it has not been done. It means that in a GUI I have to explicitely trap when the window is destroyed but I can do that.

Thanks for your interest.

Roger Woollett

User avatar
RogerW
Posts: 286
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Re: Error from pigpio.spi_close()

Wed May 13, 2015 8:40 am

Thanks for all your help. I now have ADC and Motor classes working. The ADC (MCP3008) I have tested and seems ok. The Motor clas looks good on a scope but I need to rig up an H bridge and motors to test it.

Code: Select all

import pigpio as pg
import socket as soc

class Pio():
	# to override the default host name (ip address) and port
	# assign to Pio.host and/or Pio.port in the calling file
	
	# static data
	_started = False
	_refcount = 0
	
	def __init__(self):
		Pio._refcount += 1
		self._closed = False
		if not Pio._started:
			
			try:
				Pio.host
			except AttributeError:
				Pio.host = 'localhost'
				
			try:
				Pio.port
			except AttributeError:
				Pio.port = 8888
				
			# now see if daemon is running
			try:
				socket = soc.create_connection((Pio.host,Pio.port))
			except:
				dok = False
			else:
				socket.close()
				dok = True
			
			if not dok:
				self._closed = True
				raise RuntimeError('Cannot connect to pigpio daemon')
					
			Pio.pi = pg.pi(Pio.host,Pio.port)
			Pio._started = True
			
	def close(self):
		Pio._refcount -= 1
		self._closed = True

		if Pio._refcount == 0:
			Pio.pi.stop()
			Pio._started = False
			
	def __del__(self):
		if not self._closed:
			raise RuntimeError('Close not called in Pio object')

class ADC(Pio):
	# class to access a MCP3008 A to D converter
	# pins used are:
	# Name board(gpio) - chip pin
	# MOSI	19(GPIO10)	11 - data in
	# MISO	21(GPIO9)	12 - data out
	# SCLK	23(GPIO11)	13 - clock
	# CE0	24(GPIO8)	10 - chip select
	# CE1	26(GPIO7)	10 - chip select on second chip
	# There is a second set of pins used on B+ and Pi2
	# This auxilliary port is not supported as yet
	def __init__(self,chip):
		Pio.__init__(self)
		#chip should be 0 (chip connected to CE0) or 1 (CE1)
		if chip < 0 or chip > 1:
			Pio.close(self)
			raise RuntimeError('chip must be 0 (CE0) or 1 (CE1)')
			
		self.handle = Pio.pi.spi_open(chip,50000,0)
		
	def close(self):
		Pio.pi.spi_close(self.handle)
		Pio.close(self)
		
	def read(self,channel):
		# channel corresponds to pins 1 (ch0) to 8 (ch7)
		# we have to send three bytes
		# byte 0 has 7 zeros and a start bit
		# byte 1 has the top bit set to indicate
		# single rather than differential operation
		# the next three bits contain the channel
		# the bottom four bits are zero
		# byte 2 contains zeros (don't care)
		# 3 bytes are returned
		# byte 0 is ignored
		# byte 1 contains the high 2 bits
		# byte 2 contains the low 8 bits
		if channel < 0 or channel > 7:
			raise RuntimeError('channel must be 0 - 7')
			
		(count,data) = Pio.pi.spi_xfer(self.handle,[1,(8 + channel) << 4,0])
		if count > 0:
			return (data[1] << 8) + data[2]
		else:
			return 0

class Motor(Pio):
	# Motor controls two pins which are connected to
	# one half of a dual H-Bridge controller
	# I use a SN74110NE
	def __init__(self,pin1,pin2,frequency = 15000):
		Pio.__init__(self)
		# For forward pwm to pin 1, 0 to pin2
		# for reverse pwm to pin 2, 0 to pin 1
		# speed (duty cycle) input is 0 - 100
		# When a change of direction is requested
		# the motor is stopped but only briefly.
		# Caller should arrange a longer spin down/up
		# time if required
		self._pin1 = pin1
		self._pin2 = pin2
		Pio.pi.set_mode(pin1,pg.OUTPUT)
		Pio.pi.set_mode(pin2,pg.OUTPUT)
		
		Pio.pi.set_PWM_range(pin1,100)
		Pio.pi.set_PWM_frequency(pin1,frequency)
		Pio.pi.set_PWM_range(pin2,100)
		Pio.pi.set_PWM_frequency(pin2,frequency)
		
		self._forward = True
		self.stop()
	
	def close(self):
		self.stop()
		Pio.close(self)
		
	def go(self,duty,forward = True):
		if self._forward != forward:
			self.stop()
			self._forward = forward
			
		if forward:
			Pio.pi.set_PWM_dutycycle(self._pin1,duty)
			Pio.pi.write(self._pin2,0)
		else:
			Pio.pi.set_PWM_dutycycle(self._pin2,duty)
			Pio.pi.write(self._pin1,0)
	
	def stop(self):
		Pio.pi.write(self._pin1,0)
		Pio.pi.write(self._pin2,0)
I did have to resort to a close() but Pio.__del__ makes sure it has been called so programs should be forced to clean up properly.

Incidentally - joan if you read this I think there is an error in the documentation for spi_open(). The example uses a frequency of 20000 which is below the minimum 32k. I used 50000.

Roger Woollett

User avatar
joan
Posts: 14680
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Error from pigpio.spi_close()

Wed May 13, 2015 8:52 am

RogerW wrote: ...Incidentally - joan if you read this I think there is an error in the documentation for spi_open(). The example uses a frequency of 20000 which is below the minimum 32k. I used 50000.

Roger Woollett
Thanks, I have corrected my working copy to use 50000 as well.

Return to “Python”