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

Re: New pigpio Python module

Thu Sep 24, 2015 10:27 am

Massi wrote: ...
Very clear. so my remembrance was correct. I'll try with the c module
My own take would be try Python first. If the performance isn't there convert to C using pigpiod_if. Only if that is still not good enough go to the raw C I/F.
i want to try to decode rf signals. If you remember, i already tried (some months ago..) with python and i got a full 100% cpu usage. I expect much less with a c program.
And i expect to find much more performance gain going from python to C (module) than going from C module to direct C.. am i wrong?

Thanks :)
I'll do my best to avoid the need of further help :)
I'm afraid I don't know. You would have to try both methods to be sure. I'd be guided by how easy it was to port the Python script to pigpiod_if as to whether it was worth trying that route or not.

If you feel up to posting the Python I could have a look and give it some thought.

That said I went direct from vw.py to a C I/F solution vw.c for Virtual Wire messages.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Thu Sep 24, 2015 10:34 am

joan wrote:I'd be guided by how easy it was to port the Python script to pigpiod_if as to whether it was worth trying that route or not.
If you feel up to posting the Python I could have a look and give it some thought.
Well, starting from what i've seen, the c module is quite similar to python module so _accessing gpio_ seems easy.
This should be a simple "oscilloscope" printing level changes (i've not the pi to test now)

Code: Select all

#include <stdio.h>
#include "pigpiod_if.h"

#define PIN_IN 21

void pinCallback(int gpio, int level, uint32_t tick);

int main(int argc, char * argv[])
{
	int status
	status = pigpio_start(0, 0);

	if (status < 0)
	{
		fprintf(stderr, "pigpio initialisation failed.\n");
		return 1;
	}

	/* input and pullup on gpio */
	set_mode(PIN_IN, PI_INPUT);
	set_pull_up_down(PIN_IN, PI_PUD_UP);

	/* setup a callback */
	callback(PIN_IN, EITHER_EDGE, pinCallback);

	while (1)
	{
		time_sleep(2);
	}

	pigpio_stop();

}

void pinCallback(int gpio, int level, uint32_t tick)
{
	printf("gpio %d became %d at %d\n", gpio, level, tick);
}
and this is VERY similar to python equivalent.
For the rest, i still have to think to the logic.
In python i supposed a dynamic multi dimension array (with elements [level, tick]) populated by the callback - adding elements at the end of the array - and popped by a function checking for known patterns - removing elements from the start of the array.
In c afaik it's not so easy to work with multi dimensional arrays.
I'll think about that (if you have no ready ideas :D)
That said I went direct from vw.py to a C I/F solution vw.c for.
Ok, we've found the VolksWagen fake software for diesel engines :lol:

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

Re: New pigpio Python module

Thu Sep 24, 2015 11:37 am

Yes, that will work. Tested in session underneath. Note the use of

pigs pfs 21 0 p 21 32 mils 3000 p 21 0

to generate test data.

Code: Select all

harry /ram $ cat massi.c
#include <stdio.h>
#include "pigpiod_if.h"

#define PIN_IN 21

void pinCallback(unsigned gpio, unsigned level, uint32_t tick);

int main(int argc, char * argv[])
{
   int status;
   status = pigpio_start(0, 0);

   if (status < 0)
   {
      fprintf(stderr, "pigpio initialisation failed.\n");
      return 1;
   }

   /* input and pullup on gpio */
   set_mode(PIN_IN, PI_INPUT);
   set_pull_up_down(PIN_IN, PI_PUD_UP);

   /* setup a callback */
   callback(PIN_IN, EITHER_EDGE, pinCallback);

   while (1)
   {
      time_sleep(2);
   }

   pigpio_stop();

}

void pinCallback(unsigned gpio, unsigned level, uint32_t tick)
{
   printf("gpio %d became %d at %d\n", gpio, level, tick);
}

harry /ram $ gcc -o massi massi.c -lpigpiod_if -lpthread
harry /ram $ sudo pigpiod
harry /ram $ ./massi &
[1] 1590
harry /ram $ gpio 21 became 1 at 947639426

harry /ram $ pigs pfs 21 0 p 21 32 mils 3000 p 21 0
10
gpio 21 became 0 at 973391093
gpio 21 became 1 at 973404018
gpio 21 became 0 at 973416563
gpio 21 became 1 at 973504018
gpio 21 became 0 at 973516563
gpio 21 became 1 at 973604018
gpio 21 became 0 at 973616563
gpio 21 became 1 at 973704018
gpio 21 became 0 at 973716563
gpio 21 became 1 at 973804019
gpio 21 became 0 at 973816564
gpio 21 became 1 at 973904019
gpio 21 became 0 at 973916564
gpio 21 became 1 at 974004019
gpio 21 became 0 at 974016564
gpio 21 became 1 at 974104019
gpio 21 became 0 at 974116564
gpio 21 became 1 at 974204020
gpio 21 became 0 at 974216565
gpio 21 became 1 at 974304020
gpio 21 became 0 at 974316565
gpio 21 became 1 at 974404020
gpio 21 became 0 at 974416565
gpio 21 became 1 at 974504020
gpio 21 became 0 at 974516565
gpio 21 became 1 at 974604021
gpio 21 became 0 at 974616566
gpio 21 became 1 at 974704021
gpio 21 became 0 at 974716566
gpio 21 became 1 at 974804021
gpio 21 became 0 at 974816566
gpio 21 became 1 at 974904022
gpio 21 became 0 at 974916566
gpio 21 became 1 at 975004022
gpio 21 became 0 at 975016567
gpio 21 became 1 at 975104022
gpio 21 became 0 at 975116567
gpio 21 became 1 at 975204022
gpio 21 became 0 at 975216567
gpio 21 became 1 at 975304022
gpio 21 became 0 at 975316567
gpio 21 became 1 at 975404023
gpio 21 became 0 at 975416568
gpio 21 became 1 at 975504023
gpio 21 became 0 at 975516568
gpio 21 became 1 at 975604023
gpio 21 became 0 at 975616568
gpio 21 became 1 at 975704023
gpio 21 became 0 at 975716569
gpio 21 became 1 at 975804024
gpio 21 became 0 at 975816569
gpio 21 became 1 at 975904024
gpio 21 became 0 at 975916569
gpio 21 became 1 at 976004024
gpio 21 became 0 at 976016569
gpio 21 became 1 at 976104025
gpio 21 became 0 at 976116570
gpio 21 became 1 at 976204025
gpio 21 became 0 at 976216570
gpio 21 became 1 at 976304025
gpio 21 became 0 at 976316570
harry /ram $ killall massi
[1]+  Terminated              ./massi
harry /ram $ 
harry /ram $ 

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Thu Sep 24, 2015 11:45 am

joan wrote: harry /ram $ killall massi
[1]+ Terminated ./massi
not that friendly, but i'm proud of me if i could write correct code without any test :)

this is the really rough code i was thinking about (in python) to check rf signals. as you know, cheap chinese receiver reports A LOT of noise..
Have you any advice to do that in C?
i'm a little bit scared by arrays in c..

Code: Select all

#!/usr/bin/env python3
#coding=utf-8

import pigpio
import time

gpio = 11
tick0 = None
tick1 = None
ticks = []

#values for bit parsing
minTime = 400 #filter raw noise level changes
onTolerance = 0.2 #20%
b1Tolerance = 0.1 #10%
b0Tolerance = 0.1 #10%
pauseTolerance = 0.05 #5%
onTime = 500
off1Time = 4000
off0Time = 2000
offPauseTime = 8000

def oscilloscope(gpio, level, tick):
	global tick0, tick1, ticks, minTime
	data = {}
	if level == 0:
		tick0 = tick
		if tick1 is not None:
			data['level'] = 0
			diff = pigpio.tickDiff(tick1, tick)
			data['tick'] = diff
			ticks.append(data)
#			print("HIGH|" + str(diff))
	else:
		tick1 = tick
		if tick0 is not None:
			data['level'] = 1
			diff = pigpio.tickDiff(tick0, tick)
			data['tick'] = diff
			ticks.append(data)
#			print("LOW|" + str(diff))

pi = pigpio.pi()

pi.set_mode(gpio, pigpio.INPUT)

cb = pi.callback(gpio, pigpio.EITHER_EDGE, oscilloscope)

#i'll put found bits here
parsed = ""
try:
	while True:
		if len(ticks) > 1:
			#searching first good bit
			el0 = ticks.pop(0)
			el1 = ticks.pop(0)
			while len(ticks) > 1: 
				if el0['level'] != 1:
					#something wrong, so resetting parsed string and moving on of 1
					el0 = el1
					el1 = ticks.pop(0)
					parsed = ""
				elif el0['tick'] < minTime or el0['tick'] < minTime:
					#easiest check: tick too short. Resetting and moving on
					el0 = ticks.pop(0)
					el1 = ticks.pop(0)
					parsed = ""					
				elif abs(el0['tick'] - onTime) > onTolerance * onTime:
					#so on time isn't right to be a good bit. Resetting and moving on..
					el0 = ticks.pop(0)
					el1 = ticks.pop(0)
					parsed = ""
				elif not (abs(el1['tick'] - off0Time) < b0Tolerance * off0Time) and not(abs(el1['tick'] - off1Time) < b1Tolerance * off1Time) and not(abs(el1['tick'] - offPauseTime) < pauseTolerance * offPauseTime):
					#so off time isn't right to be a good bit. Resetting and moving on..
					el0 = ticks.pop(0)
					el1 = ticks.pop(0)
					parsed = ""
				else:
					#wow found a good bit!
					if abs(el1['tick'] - off0Time) < b0Tolerance * off0Time:
						parsed += "0"
					elif abs(el1['tick'] - off1Time) < b1Tolerance * off1Time:
						parsed += "1"
					elif abs(el1['tick'] - offPauseTime) < pauseTolerance * offPauseTime:
						parsed += "P"
					#trashing levels and going on
					el0 = ticks.pop(0)
					el1 = ticks.pop(0)
						
				#now debugging
				if len(parsed) > 10:
					print(parsed)
				if len(ticks) > 100:
					print(len(ticks))
			time.sleep(0.001) #let's see if this leaves some juice for other tasks..
	cb.cancel() # cancel callback
	pi.stop()
				
except Exception as e:
	cb.cancel() # cancel callback
	pi.stop()
Edit to add: obviously this is written to work only for some specific types of signals (in this case, OOK signals with specific levels duration)

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

Re: New pigpio Python module

Thu Sep 24, 2015 9:33 pm

I've checked that the following compiles but apart from that it is untested. Hopefully it will provide a starting point.

Code: Select all

/*
#!/usr/bin/env python3
#coding=utf-8

import pigpio
import time
*/

#include <stdio.h>
#include <stdlib.h>

#include <pigpiod_if.h>
/*
gcc -o massi_4 massi_4.c -lpigpiod_if -lpthread
*/
/*
gpio = 11
tick0 = None
tick1 = None
ticks = []
*/

int gpio=11;

typedef struct
{
   int level;
   int tick;
} tick_t;

#define MAX_DATA 1000

tick_t ticks[MAX_DATA];

int ticks_head = 0;
int ticks_tail = 0;
int ticks_entries = 0;

/*
#values for bit parsing
minTime = 400 #filter raw noise level changes
onTolerance = 0.2 #20%
b1Tolerance = 0.1 #10%
b0Tolerance = 0.1 #10%
pauseTolerance = 0.05 #5%
onTime = 500
off1Time = 4000
off0Time = 2000
offPauseTime = 8000
*/

int minTime = 400; // filter raw noise level changes
float onTolerance = 0.2; // 20%
float b1Tolerance = 0.1; // 10%
float b0Tolerance = 0.1; // 10%
float pauseTolerance = 0.05; // 5%
int onTime = 500;
int off1Time = 4000;
int off0Time = 2000;
int offPauseTime = 8000;

/*
def oscilloscope(gpio, level, tick):
   global tick0, tick1, ticks, minTime
   data = {}
   if level == 0:
      tick0 = tick
      if tick1 is not None:
         data['level'] = 0
         diff = pigpio.tickDiff(tick1, tick)
         data['tick'] = diff
         ticks.append(data)
#         print("HIGH|" + str(diff))
   else:
      tick1 = tick
      if tick0 is not None:
         data['level'] = 1
         diff = pigpio.tickDiff(tick0, tick)
         data['tick'] = diff
         ticks.append(data)
#         print("LOW|" + str(diff))
*/

void put(int level, int tick)
{
   if (ticks_entries <= MAX_DATA)
   {
      ticks[ticks_head].level = level;
      ticks[ticks_head].tick = tick;
      ++ticks_entries;
      if (++ticks_head >= MAX_DATA) ticks_head = 0;
   }
}

tick_t get(void)
{
   tick_t t={0, 0};

   if (ticks_entries)
   {
      t = ticks[ticks_tail];
      --ticks_entries;
      if (++ticks_tail >= MAX_DATA) ticks_tail = 0;
   }
   return t;
}

void oscilloscope(unsigned gpio, unsigned level, uint32_t tick)
{
   static uint32_t tick0, tick0_inited=0, tick1, tick1_inited=0;

   if (level == 0)
   {
      tick0 = tick;
      tick0_inited = 1;
      if (tick1_inited) put(0, tick-tick1);
   }
   else
   {
      tick1 = tick;
      tick1_inited = 1;
      if (tick0_inited) put(1, tick-tick0);
   }
}
/*
pi = pigpio.pi()

pi.set_mode(gpio, pigpio.INPUT)

cb = pi.callback(gpio, pigpio.EITHER_EDGE, oscilloscope)
*/

int main(int argc, char *argv[])
{
   int status, cb;
   char parsed[1024];
   tick_t el0, el1;
   int parsed_pos;

   status = pigpio_start(0, 0); // connect to local Pi

   if (status == 0)
   {
      set_mode(gpio, PI_INPUT);

      cb = callback(gpio, EITHER_EDGE, oscilloscope);

      /*
      #i'll put found bits here
      parsed = ""
      try:
         while True:
      */
      parsed_pos = 0;

      while (1)
      {
         /*
         if len(ticks) > 1:
            #searching first good bit
            el0 = ticks.pop(0)
            el1 = ticks.pop(0)
         */
         if (ticks_entries > 1)
         {
            el0 = get();
            el1 = get();
            /*
            while len(ticks) > 1:
            */
            while (ticks_entries > 1)
            {
               /*
               if el0['level'] != 1:
                  #something wrong, so resetting parsed string and moving on of 1
                  el0 = el1
                  el1 = ticks.pop(0)
                  parsed = ""
               */
               if (el0.level != 1)
               {
                  el0 = el1;
                  el1 = get();
                  parsed_pos = 0;
               }
               /*
               elif el0['tick'] < minTime or el0['tick'] < minTime:
                  #easiest check: tick too short. Resetting and moving on
                  el0 = ticks.pop(0)
                  el1 = ticks.pop(0)
                  parsed = ""               
               */
               else if (el0.tick < minTime || el1.tick < minTime)
               {
                  el0 = get();
                  el1 = get();
                  parsed_pos = 0;
               }
               /*
               elif abs(el0['tick'] - onTime) > onTolerance * onTime:
                  #so on time isn't right to be a good bit. Resetting and moving on..
                  el0 = ticks.pop(0)
                  el1 = ticks.pop(0)
                  parsed = ""
               */
               else if (abs(el0.tick - onTime) > (onTolerance * onTime))
               {
                  el0 = get();
                  el1 = get();
                  parsed_pos = 0;
               }
               /*
               elif not (abs(el1['tick'] - off0Time) < b0Tolerance * off0Time) and
                    not (abs(el1['tick'] - off1Time) < b1Tolerance * off1Time) and
                    not(abs(el1['tick'] - offPauseTime) < pauseTolerance * offPauseTime):
                  #so off time isn't right to be a good bit. Resetting and moving on..
                  el0 = ticks.pop(0)
                  el1 = ticks.pop(0)
                  parsed = ""
               */
               else if ( !(abs(el1.tick - off0Time) < (b0Tolerance * off0Time)) &&
                         !(abs(el1.tick - off1Time) < (b1Tolerance * off1Time)) &&
                         !(abs(el1.tick - offPauseTime) < (pauseTolerance * offPauseTime)))
               {
                  el0 = get();
                  el1 = get();
                  parsed_pos = 0;
               }
               /*
               else:
                  #wow found a good bit!
                  if abs(el1['tick'] - off0Time) < b0Tolerance * off0Time:
                     parsed += "0"
                  elif abs(el1['tick'] - off1Time) < b1Tolerance * off1Time:
                     parsed += "1"
                  elif abs(el1['tick'] - offPauseTime) < pauseTolerance * offPauseTime:
                     parsed += "P"
               */
               else
               {
                  if (abs(el1.tick - off0Time) < (b0Tolerance * off0Time))
                     parsed[parsed_pos]='0';
                  else if (abs(el1.tick - off1Time) < (b1Tolerance * off1Time))
                     parsed[parsed_pos]='1';
                  else if (abs(el1.tick - offPauseTime) < (pauseTolerance * offPauseTime))
                     parsed[parsed_pos]='P';
                  else
                     parsed[parsed_pos]='X';
                  ++parsed_pos;
                  /*
                  #trashing levels and going on
                  el0 = ticks.pop(0)
                  el1 = ticks.pop(0)
                  */
                  el0 = get();
                  el1 = get();
               }
               /*
               #now debugging
               if len(parsed) > 10:
                  print(parsed)
               if len(ticks) > 100:
                  print(len(ticks))
               */
               if (parsed_pos > 10)
               {
                  parsed[parsed_pos] = 0; // terminate string
                  printf(parsed);
               }
               if (ticks_entries > 100)
                  printf("ticks entries=%d\n", ticks_entries);
            }
         }
         /*
         time.sleep(0.001) #let's see if this leaves some juice for other tasks..
         */
         time_sleep(0.001);
      }
      callback_cancel(cb); //cancel callback
      pigpio_stop();
   }
}

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Fri Sep 25, 2015 5:54 am

wow thank you!
i'll give it a look when the new raspi will arrive :(

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Sep 29, 2015 2:00 pm

Hello again mate,
no, i still have not tested the code after having burnt out my berry :)
but i was trying to look inside the python module (bad idea: a similar php module, for easy commands like set gpio or read gpio..) and i'm stuck on how you structured socket connections.
If you have 20 seconds, could you please explain to a noob what's the use of:
- socklock
- callback_thread

my ideas:
- socklock: used then when sending messages on the socket to acquire/release
- for every istance of pigpio two sockets get connected (one for commands, one for notifications _of callbacks_)

how much am i wrong?
is the second socket used ONLY for notifications like the level changes of GPIO?

Thanks as always :)
Last edited by Massi on Thu Oct 01, 2015 3:03 pm, edited 1 time in total.

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

Re: New pigpio Python module

Tue Sep 29, 2015 2:51 pm

A socket is used to send commands to and get responses from the daemon. Each command is four words long consisting of a command code and up to three parameters p1, p2, and p3. The daemon returns the sent command but replaces p3 with the response or an error code. A valid response is always >=0, an error is always <0.

E.g. to set the dutycycle frequency for gpio 4 to 300 the command is 7 (PI_CMD_PFS) with parameters 4, 300, and 0. The returned data would be 7, 4, 300, 320, where 320 is the frequency actually set.

This is fine in a single threaded script. A response follows the command and all is well. However people do like their threads. With separate threads it is possible that one threads response was to another threads command. This can easily lead to errors. So socklock. The socket is locked so that for the duration of a command/response only one thread can talk to the daemon.

For the separate callback thread/socket, some history.

Notifications are a method of getting updates when one or more GPIO change level.

The procedure is as follows:

Notify Open: returns a handle to be used on subsequent calls. The handle is a small number x >=0. A pipe is created at /dev/pigpiox to receive the reports.

Notify Begin specifying the handle (x) and the GPIO of interest. GPIO changes are then written to /dev/pigpiox in the form of 12 byte reports.

Notify Pause (optional) specifying the handle (x): to temporarily pause (discard) GPIO reports until another notification begin.

Notify Close specifying the handle (x). Notifications are stopped, the pipe (/dev/pigpiox) is closed, and the handle x is put on the free list of handles.

This is fine as far as it goes but pipes are only accessible from the local Pi.

So a modification was made to allow the use of sockets.

If a NOIB (Notification Open In Band) command is received on a socket the notification open does not open a pipe. Instead any data which would be sent to the pipe are now sent to the socket. You could still send commands to the socket. The problem is that the command response might be intermingled with GPIO notification messages. It might be possible to disentangle the mess but it is simpler just to use another socket for commands. So use two sockets, one to receive GPIO state changes, and one to send commands. That's what I do in the Python module, in piscope, and in the pigpiod_if library.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Sep 29, 2015 2:57 pm

very clear.
so i think that for php the lock is not needed as it's quite far from managing threads in a proper way.
i'll see if at least i can get it connected :)

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

Re: New pigpio Python module

Tue Sep 29, 2015 3:23 pm

Massi wrote:very clear.
so i think that for php the lock is not needed as it's quite far from managing threads in a proper way.
i'll see if at least i can get it connected :)
Have a look at pigpiod_if.c. That is probably the simplest example and it does not use locks either.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Wed Sep 30, 2015 8:40 pm

well, nice, it seems i can get it to work.. set and get mode, set pullup, set and get gpio value.. not bad for easy things :)
need to do a little error checking, but it seems ok.

is there a reason i can't get the -4 error (bad GPIO) even if i try to use casual gpio numbers (i.e.: 100) getting instead the -41 (not permitted) error?

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

Re: New pigpio Python module

Wed Sep 30, 2015 9:04 pm

Massi wrote:well, nice, it seems i can get it to work.. set and get mode, set pullup, set and get gpio value.. not bad for easy things :)
need to do a little error checking, but it seems ok.

is there a reason i can't get the -4 error (bad GPIO) even if i try to use casual gpio numbers (i.e.: 100) getting instead the -41 (not permitted) error?
The permissions check is done first and out or range GPIO numbers are rejected in that check.

I think you are correct and bad GPIO number should be returned rather than no permission for illegal GPIOs. No permission sort of implies you can get the permission, which can never be the case for illegal GPIO numbers.

I'll see how easy it is to make the change.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Wed Sep 30, 2015 9:44 pm

joan wrote:The permissions check is done first and out or range GPIO numbers are rejected in that check.

I think you are correct and bad GPIO number should be returned rather than no permission for illegal GPIOs. No permission sort of implies you can get the permission, which can never be the case for illegal GPIO numbers.

I'll see how easy it is to make the change.
that wasn't a bug report :) i was just wondering in what case i could get the -4 error if not using wrong gpio number..

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Thu Oct 01, 2015 8:05 pm

i've noticed that in some cases the "correct" -3 is returned when using wrong gpio.
ie: set_mode, returns -41
read, returns -3

btw, i don't know if this can be useful to anyone, but for very easy gpio toggling this is working in php (nice it can be used on a webserver different from the pi!)
Adding other function is not that difficult or that long, but i think that all the "callback" part (or advanced) is not interesting for web use.. for scripting use there's python :)
for sure i'll add i2c functions..

thank you joan for the wonderful pigpio and all the support (and teaching) :)

Ps: it seems to me that there's no need of managing signed/unsigned integers in php (you do it in python), but i can have lost something :)

since i can't attach a file, the code..
Usage example

Code: Select all

//connect to pi
$pi = new pigpio('192.168.22.202');

$pi->set_mode(11, $pi->INPUT);
$pi->set_pull_up_down(11, $pi->PUD_DOWN);
echo $pi->read(11); //returns 0

$pi->set_pull_up_down(11, $pi->PUD_OFF);
$pi->set_mode(11, $pi->OUTPUT);
$pi->write(11,1);
echo $pi->read(11); //returns 1

echo "Hardware rev: ".$pi->get_hardware_revision();
echo "<br/>";
echo "Pigpio ver: ".$pi->get_pigpio_version();
echo "<br/>";
echo "Bank 1: ".$pi->read_bank_1();
echo "<br/>";
echo "Bank 2: ".$pi->read_bank_2();
echo "<br/>";

//unset istance and let the __destruct close socket
unset($pi);
Class code

Code: Select all

class pigpio {
	
	#misc
	private $version = "0.1 alpha";
	#exceptions on error
	public $pigpioExceptions = True; //raise exception when pigpiod returns an error
	public $socketExceptions = True; //raise exception on warning on socket. Failure on socket ALWAYS raise exceptions
	
	# pigpio command numbers
	private $_PI_CMD_MODES 	=	0;
	private $_PI_CMD_MODEG 	=	1;
	private $_PI_CMD_PUD	=	2;
	private $_PI_CMD_READ	=	3;
	private $_PI_CMD_WRITE	=	4;
	private $_PI_CMD_BR1	=	10;
	private $_PI_CMD_BR2	=	11;
	private $_PI_CMD_BC1	=	12;
	private $_PI_CMD_BC2	=	13;
	private $_PI_CMD_BS1	=	14;
	private $_PI_CMD_BS2	=	15;
	private $_PI_CMD_HWVER	=	17;
	private $_PI_CMD_PIGPV	=	26;

	# gpio modes
	public $INPUT  			=	0;
	public $OUTPUT 			=	1;
	public $ALT0   			=	4;
	public $ALT1   			=	5;
	public $ALT2   			=	6;
	public $ALT3   			=	7;
	public $ALT4   			=	3;
	public $ALT5   			=	2;
	
	# gpio Pull Up Down
	public $PUD_OFF  		=	0;
	public $PUD_DOWN 		=	1;
	public $PUD_UP   		=	2;
	
	# pigpio error numbers
	const _PI_INIT_FAILED     =-1;
	const PI_BAD_USER_GPIO    =-2;
	const PI_BAD_GPIO         =-3;
	const PI_BAD_MODE         =-4;
	const PI_BAD_LEVEL        =-5;
	const PI_BAD_PUD          =-6;
	const PI_BAD_PULSEWIDTH   =-7;
	const PI_BAD_DUTYCYCLE    =-8;
	const _PI_BAD_TIMER       =-9;
	const _PI_BAD_MS          =-10;
	const _PI_BAD_TIMETYPE    =-11;
	const _PI_BAD_SECONDS     =-12;
	const _PI_BAD_MICROS      =-13;
	const _PI_TIMER_FAILED    =-14;
	const PI_BAD_WDOG_TIMEOUT =-15;
	const _PI_NO_ALERT_FUNC   =-16;
	const _PI_BAD_CLK_PERIPH  =-17;
	const _PI_BAD_CLK_SOURCE  =-18;
	const _PI_BAD_CLK_MICROS  =-19;
	const _PI_BAD_BUF_MILLIS  =-20;
	const PI_BAD_DUTYRANGE    =-21;
	const _PI_BAD_SIGNUM      =-22;
	const _PI_BAD_PATHNAME    =-23;
	const PI_NO_HANDLE        =-24;
	const PI_BAD_HANDLE       =-25;
	const _PI_BAD_IF_FLAGS    =-26;
	const _PI_BAD_CHANNEL     =-27;
	const _PI_BAD_PRIM_CHANNEL=-27;
	const _PI_BAD_SOCKET_PORT =-28;
	const _PI_BAD_FIFO_COMMAND=-29;
	const _PI_BAD_SECO_CHANNEL=-30;
	const _PI_NOT_INITIALISED =-31;
	const _PI_INITIALISED     =-32;
	const _PI_BAD_WAVE_MODE   =-33;
	const _PI_BAD_CFG_INTERNAL=-34;
	const PI_BAD_WAVE_BAUD    =-35;
	const PI_TOO_MANY_PULSES  =-36;
	const PI_TOO_MANY_CHARS   =-37;
	const PI_NOT_SERIAL_GPIO  =-38;
	const _PI_BAD_SERIAL_STRUC=-39;
	const _PI_BAD_SERIAL_BUF  =-40;
	const PI_NOT_PERMITTED    =-41;
	const PI_SOME_PERMITTED   =-42;
	const PI_BAD_WVSC_COMMND  =-43;
	const PI_BAD_WVSM_COMMND  =-44;
	const PI_BAD_WVSP_COMMND  =-45;
	const PI_BAD_PULSELEN     =-46;
	const PI_BAD_SCRIPT       =-47;
	const PI_BAD_SCRIPT_ID    =-48;
	const PI_BAD_SER_OFFSET   =-49;
	const PI_GPIO_IN_USE      =-50;
	const PI_BAD_SERIAL_COUNT =-51;
	const PI_BAD_PARAM_NUM    =-52;
	const PI_DUP_TAG          =-53;
	const PI_TOO_MANY_TAGS    =-54;
	const PI_BAD_SCRIPT_CMD   =-55;
	const PI_BAD_VAR_NUM      =-56;
	const PI_NO_SCRIPT_ROOM   =-57;
	const PI_NO_MEMORY        =-58;
	const PI_SOCK_READ_FAILED =-59;
	const PI_SOCK_WRIT_FAILED =-60;
	const PI_TOO_MANY_PARAM   =-61;
	const PI_NOT_HALTED       =-62;
	const PI_BAD_TAG          =-63;
	const PI_BAD_MICS_DELAY   =-64;
	const PI_BAD_MILS_DELAY   =-65;
	const PI_BAD_WAVE_ID      =-66;
	const PI_TOO_MANY_CBS     =-67;
	const PI_TOO_MANY_OOL     =-68;
	const PI_EMPTY_WAVEFORM   =-69;
	const PI_NO_WAVEFORM_ID   =-70;
	const PI_I2C_OPEN_FAILED  =-71;
	const PI_SER_OPEN_FAILED  =-72;
	const PI_SPI_OPEN_FAILED  =-73;
	const PI_BAD_I2C_BUS      =-74;
	const PI_BAD_I2C_ADDR     =-75;
	const PI_BAD_SPI_CHANNEL  =-76;
	const PI_BAD_FLAGS        =-77;
	const PI_BAD_SPI_SPEED    =-78;
	const PI_BAD_SER_DEVICE   =-79;
	const PI_BAD_SER_SPEED    =-80;
	const PI_BAD_PARAM        =-81;
	const PI_I2C_WRITE_FAILED =-82;
	const PI_I2C_READ_FAILED  =-83;
	const PI_BAD_SPI_COUNT    =-84;
	const PI_SER_WRITE_FAILED =-85;
	const PI_SER_READ_FAILED  =-86;
	const PI_SER_READ_NO_DATA =-87;
	const PI_UNKNOWN_COMMAND  =-88;
	const PI_SPI_XFER_FAILED  =-89;
	const _PI_BAD_POINTER     =-90;
	const PI_NO_AUX_SPI       =-91;
	const PI_NOT_PWM_GPIO     =-92;
	const PI_NOT_SERVO_GPIO   =-93;
	const PI_NOT_HCLK_GPIO    =-94;
	const PI_NOT_HPWM_GPIO    =-95;
	const PI_BAD_HPWM_FREQ    =-96;
	const PI_BAD_HPWM_DUTY    =-97;
	const PI_BAD_HCLK_FREQ    =-98;
	const PI_BAD_HCLK_PASS    =-99;
	const PI_HPWM_ILLEGAL     =-100;
	const PI_BAD_DATABITS     =-101;
	const PI_BAD_STOPBITS     =-102;
	const PI_MSG_TOOBIG       =-103;
	const PI_BAD_MALLOC_MODE  =-104;

	# pigpio error text
	private $_errors = Array(
		self::_PI_INIT_FAILED	 =>"pigpio initialisation failed",
		self::PI_BAD_USER_GPIO    =>"gpio not 0-31",
		self::PI_BAD_GPIO         =>"gpio not 0-53",
		self::PI_BAD_MODE         =>"mode not 0-7",
		self::PI_BAD_LEVEL        =>"level not 0-1",
		self::PI_BAD_PUD          =>"pud not 0-2",
		self::PI_BAD_PULSEWIDTH   =>"pulsewidth not 0 or 500-2500",
		self::PI_BAD_DUTYCYCLE    =>"dutycycle not 0-range (default 255)",
		self::_PI_BAD_TIMER       =>"timer not 0-9",
		self::_PI_BAD_MS          =>"ms not 10-60000",
		self::_PI_BAD_TIMETYPE    =>"timetype not 0-1",
		self::_PI_BAD_SECONDS     =>"seconds < 0",
		self::_PI_BAD_MICROS      =>"micros not 0-999999",
		self::_PI_TIMER_FAILED    =>"gpioSetTimerFunc failed",
		self::PI_BAD_WDOG_TIMEOUT =>"timeout not 0-60000",
		self::_PI_NO_ALERT_FUNC   =>"DEPRECATED",
		self::_PI_BAD_CLK_PERIPH  =>"clock peripheral not 0-1",
		self::_PI_BAD_CLK_SOURCE  =>"DEPRECATED",
		self::_PI_BAD_CLK_MICROS  =>"clock micros not 1, 2, 4, 5, 8, or 10",
		self::_PI_BAD_BUF_MILLIS  =>"buf millis not 100-10000",
		self::PI_BAD_DUTYRANGE    =>"dutycycle range not 25-40000",
		self::_PI_BAD_SIGNUM      =>"signum not 0-63",
		self::_PI_BAD_PATHNAME    =>"can't open pathname",
		self::PI_NO_HANDLE        =>"no handle available",
		self::PI_BAD_HANDLE       =>"unknown handle",
		self::_PI_BAD_IF_FLAGS    =>"ifFlags > 3",
		self::_PI_BAD_CHANNEL     =>"DMA channel not 0-14",
		self::_PI_BAD_SOCKET_PORT =>"socket port not 1024-30000",
		self::_PI_BAD_FIFO_COMMAND=>"unknown fifo command",
		self::_PI_BAD_SECO_CHANNEL=>"DMA secondary channel not 0-6",
		self::_PI_NOT_INITIALISED =>"function called before gpioInitialise",
		self::_PI_INITIALISED     =>"function called after gpioInitialise",
		self::_PI_BAD_WAVE_MODE   =>"waveform mode not 0-1",
		self::_PI_BAD_CFG_INTERNAL=>"bad parameter in gpioCfgInternals call",
		self::PI_BAD_WAVE_BAUD    =>"baud rate not 50-250000(RX)/1000000(TX)",
		self::PI_TOO_MANY_PULSES  =>"waveform has too many pulses",
		self::PI_TOO_MANY_CHARS   =>"waveform has too many chars",
		self::PI_NOT_SERIAL_GPIO  =>"no serial read in progress on gpio",
		self::PI_NOT_PERMITTED    =>"no permission to update gpio",
		self::PI_SOME_PERMITTED   =>"no permission to update one or more gpios",
		self::PI_BAD_WVSC_COMMND  =>"bad WVSC subcommand",
		self::PI_BAD_WVSM_COMMND  =>"bad WVSM subcommand",
		self::PI_BAD_WVSP_COMMND  =>"bad WVSP subcommand",
		self::PI_BAD_PULSELEN     =>"trigger pulse length not 1-100",
		self::PI_BAD_SCRIPT       =>"invalid script",
		self::PI_BAD_SCRIPT_ID    =>"unknown script id",
		self::PI_BAD_SER_OFFSET   =>"add serial data offset > 30 minute",
		self::PI_GPIO_IN_USE      =>"gpio already in use",
		self::PI_BAD_SERIAL_COUNT =>"must read at least a byte at a time",
		self::PI_BAD_PARAM_NUM    =>"script parameter id not 0-9",
		self::PI_DUP_TAG          =>"script has duplicate tag",
		self::PI_TOO_MANY_TAGS    =>"script has too many tags",
		self::PI_BAD_SCRIPT_CMD   =>"illegal script command",
		self::PI_BAD_VAR_NUM      =>"script variable id not 0-149",
		self::PI_NO_SCRIPT_ROOM   =>"no more room for scripts",
		self::PI_NO_MEMORY        =>"can't allocate temporary memory",
		self::PI_SOCK_READ_FAILED =>"socket read failed",
		self::PI_SOCK_WRIT_FAILED =>"socket write failed",
		self::PI_TOO_MANY_PARAM   =>"too many script parameters (> 10)",
		self::PI_NOT_HALTED       =>"script already running or failed",
		self::PI_BAD_TAG          =>"script has unresolved tag",
		self::PI_BAD_MICS_DELAY   =>"bad MICS delay (too large)",
		self::PI_BAD_MILS_DELAY   =>"bad MILS delay (too large)",
		self::PI_BAD_WAVE_ID      =>"non existent wave id",
		self::PI_TOO_MANY_CBS     =>"No more CBs for waveform",
		self::PI_TOO_MANY_OOL     =>"No more OOL for waveform",
		self::PI_EMPTY_WAVEFORM   =>"attempt to create an empty waveform",
		self::PI_NO_WAVEFORM_ID   =>"No more waveform ids",
		self::PI_I2C_OPEN_FAILED  =>"can't open I2C device",
		self::PI_SER_OPEN_FAILED  =>"can't open serial device",
		self::PI_SPI_OPEN_FAILED  =>"can't open SPI device",
		self::PI_BAD_I2C_BUS      =>"bad I2C bus",
		self::PI_BAD_I2C_ADDR     =>"bad I2C address",
		self::PI_BAD_SPI_CHANNEL  =>"bad SPI channel",
		self::PI_BAD_FLAGS        =>"bad i2c/spi/ser open flags",
		self::PI_BAD_SPI_SPEED    =>"bad SPI speed",
		self::PI_BAD_SER_DEVICE   =>"bad serial device name",
		self::PI_BAD_SER_SPEED    =>"bad serial baud rate",
		self::PI_BAD_PARAM        =>"bad i2c/spi/ser parameter",
		self::PI_I2C_WRITE_FAILED =>"I2C write failed",
		self::PI_I2C_READ_FAILED  =>"I2C read failed",
		self::PI_BAD_SPI_COUNT    =>"bad SPI count",
		self::PI_SER_WRITE_FAILED =>"ser write failed",
		self::PI_SER_READ_FAILED  =>"ser read failed",
		self::PI_SER_READ_NO_DATA =>"ser read no data available",
		self::PI_UNKNOWN_COMMAND  =>"unknown command",
		self::PI_SPI_XFER_FAILED  =>"SPI xfer/read/write failed",
		self::_PI_BAD_POINTER     =>"bad (NULL) pointer",
		self::PI_NO_AUX_SPI       =>"need a A+/B+/Pi2 for auxiliary SPI",
		self::PI_NOT_PWM_GPIO     =>"gpio is not in use for PWM",
		self::PI_NOT_SERVO_GPIO   =>"gpio is not in use for servo pulses",
		self::PI_NOT_HCLK_GPIO    =>"gpio has no hardware clock",
		self::PI_NOT_HPWM_GPIO    =>"gpio has no hardware PWM",
		self::PI_BAD_HPWM_FREQ    =>"hardware PWM frequency not 1-125M",
		self::PI_BAD_HPWM_DUTY    =>"hardware PWM dutycycle not 0-1M",
		self::PI_BAD_HCLK_FREQ    =>"hardware clock frequency not 4689-250M",
		self::PI_BAD_HCLK_PASS    =>"need password to use hardware clock 1",
		self::PI_HPWM_ILLEGAL     =>"illegal, PWM in use for main clock",
		self::PI_BAD_DATABITS     =>"serial data bits not 1-32",
		self::PI_BAD_STOPBITS     =>"serial (half) stop bits not 2-8",
		self::PI_MSG_TOOBIG       =>"socket/pipe message too big",
		self::PI_BAD_MALLOC_MODE  =>"bad memory allocation mode"
	);
	
	public function __construct($address = null, $port = null) {
		//address can be specified or set in an env var. If not, used localhost
		if (!isset($address)){
			$address = (getenv("PIGPIO_ADDR") ? getenv("PIGPIO_ADDR") : "localhost");
		}
		
		//port can be specified or set in an env var. If not, used standard 8888
		if (!isset($port)){
			$port = (getenv("PIGPIO_PORT") ? getenv("PIGPIO_PORT") : 8888);
		}
		if($this->pigpioExceptions === True){
			//need to redefine warning management
			set_error_handler(array($this, 'warningHandler'), E_WARNING);
		}
		
		#creating socket
		if(FALSE === ($this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))){
			Throw new Exception("Failed creating socket");
		};

		# Disable the Nagle algorithm and setting timeout
		if(FALSE === socket_set_option($this->socket, SOL_TCP, TCP_NODELAY,1)
		|| FALSE === socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 2, 'usec' => 0))
		){
			Throw new Exception("Failed setting socket options");
		};
		;

		#connecting
		if(FALSE === socket_connect($this->socket, $address, $port)){
			Throw new Exception("Failed connecting socket");
		};

		if($this->pigpioExceptions === True){
			//need to redefine warning management
			restore_error_handler();
		}

	}

	private function _pigpio_command($socket, $command, $param1, $param2){
		/*
		Runs a pigpio socket command.
		$socket	:= command socket.
		$command:= the command to be executed.
		$param1	:= command parameter 1 (if applicable).
		$param2	:= command parameter 2 (if applicable).
		*/
		
		$strSent = pack('IIII', $command, $param1, $param2, 0);
		$bytesSent = socket_send($socket, $strSent, strlen($strSent), 0);
		
		$recvBuf = null;
		$recvBytes = socket_recv($socket, $recvBuf, 16, 0);
		
		$received = unpack('I3dummy/Ires', $recvBuf);
		#checking the correct answer
		if($received['dummy1'] != $command) print("Wrong command");
		if($received['dummy2'] != $param1) print("Wrong param1");
		if($received['dummy3'] != $param2) print("Wrong param2");
		#checking value returned
		return $this->check_error($received['res']);
	}
	
	public function set_mode($gpio, $mode){
		/*
		Sets the gpio mode.
		$gpio:= 0-53.
		$mode:= INPUT, OUTPUT, ALT0, ALT1, ALT2, ALT3, ALT4, ALT5.
		...
		$pi->set_mode( 4, $pi->INPUT);  # gpio  4 as input
		$pi->set_mode(17, $pi->OUTPUT); # gpio 17 as output
		$pi->set_mode(24, $pi->ALT2);   # gpio 24 as ALT2
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_MODES, $gpio, $mode));	
	}

	public function get_mode($gpio){
		/*
		Returns the gpio mode.
		$gpio:= 0-53.
		Returns a value as follows
		0 = INPUT
		1 = OUTPUT
		2 = ALT5
		3 = ALT4
		4 = ALT0
		5 = ALT1
		6 = ALT2
		7 = ALT3
		...
		print(pi->get_mode(0));
		4		
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_MODEG, $gpio, 0));	
	}

	public function set_pull_up_down($gpio, $pud){
		/*
		Sets or clears the internal gpio pull-up/down resistor.
		$gpio:= 0-53.
		$pud:= PUD_UP, PUD_DOWN, PUD_OFF.

		pi.set_pull_up_down(17, pigpio.PUD_OFF)
		pi.set_pull_up_down(23, pigpio.PUD_UP)
		pi.set_pull_up_down(24, pigpio.PUD_DOWN)
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_PUD, $gpio, $pud));	
	}
	
	public function read($gpio){
		/*
		Returns the gpio level.
		$gpio:= 0-53.
		...
		pi.set_mode(23, pigpio.INPUT)
		pi.set_pull_up_down(23, pigpio.PUD_DOWN)
		print(pi.read(23))
		0
		pi.set_pull_up_down(23, pigpio.PUD_UP)
		print(pi.read(23))
		1
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_READ, $gpio, 0));	
	}

	public function write($gpio, $level){
		/*
		Sets the gpio level.
		$gpio:= 0-53.
		$level:= 0, 1.
		If PWM or servo pulses are active on the gpio they are
		switched off.
		...
		pi.set_mode(17, pigpio.OUTPUT)
		pi.write(17,0)
		print(pi.read(17))
		0
		pi.write(17,1)
		print(pi.read(17))
		1
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_WRITE, $gpio, $level));	
	}
		
	public function get_hardware_revision(){
		/*
		Returns the Pi's hardware revision number.
		The hardware revision is the last few characters on the revision line of /proc/cpuinfo.
		The revision number can be used to determine the assignment of gpios to pins (see [*gpio*]).
		There are at least three types of board.
		Type 1 boards have hardware revision numbers of 2 and 3.
		Type 2 boards have hardware revision numbers of 4, 5, 6, and 15.
		Type 3 boards have hardware revision numbers of 16 or greater.
		If the hardware revision can not be found or is not a valid hexadecimal number the function returns 0.
		...
		print($pi->get_hardware_revision());
		2
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_HWVER, 0, 0));	
	}

	public function get_pigpio_version(){
		/*
		Returns the pigpio software version.
		...
		print($pi->get_pigpio_version());
		*/
		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_PIGPV, 0, 0));	
	}


	public function read_bank_1(){
		/*
		Returns the levels of the bank 1 gpios (gpios 0-31).
		The returned 32 bit integer has a bit set if the corresponding gpio is high.  Gpio n has bit value (1<<n).
		...
		print(pi->read_bank_1())
		10010100000011100100001001111
		*/
		return(str_pad(decbin($this -> _pigpio_command($this->socket, $this->_PI_CMD_BR1, 0, 0)), 32, "0", STR_PAD_LEFT));	
	}
 
	public function read_bank_2(){
		/*
		Returns the levels of the bank 2 gpios (gpios 32-53).
		The returned 32 bit integer has a bit set if the corresponding gpio is high.  Gpio n has bit value (1<<n).
		...
		print(pi->read_bank_2())
		10010100000011100100001001111
		*/
		return(str_pad(decbin($this -> _pigpio_command($this->socket, $this->_PI_CMD_BR2, 0, 0)), 32, "0", STR_PAD_LEFT));	
	}

	public function clear_bank_1($bits){
		/*
		Clears gpios 0-31 if the corresponding bit in bits is set.
		bits:= a 32 bit mask with 1 set if the corresponding gpio is to be cleared.

		A returned status of PI_SOME_PERMITTED indicates that the user is not allowed to write to one or more of the gpios.
		...
		$pi->clear_bank_1(bindec("111110010000"))
		*/
 		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_BC1, $bits, 0));	
	}
	
	public function clear_bank_2($bits){
		/*
		Clears gpios 32-53 if the corresponding bit in bits is set.
		bits:= a 32 bit mask with 1 set if the corresponding gpio is to be cleared.

		A returned status of PI_SOME_PERMITTED indicates that the user is not allowed to write to one or more of the gpios.
		...
		$pi->clear_bank_2(bindec("111110010000"))
		*/
 		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_BC2, $bits, 0));	
	}

	public function set_bank_1($bits){
		/*
		Clears gpios 0-31 if the corresponding bit in bits is set.
		bits:= a 32 bit mask with 1 set if the corresponding gpio is to be set.

		A returned status of PI_SOME_PERMITTED indicates that the user is not allowed to write to one or more of the gpios.
		...
		$pi->set_bank_1(bindec("111110010000"))
		*/
 		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_BS1, $bits, 0));	
	}
	
	public function set_bank_2($bits){
		/*
		Clears gpios 32-53 if the corresponding bit in bits is set.
		bits:= a 32 bit mask with 1 set if the corresponding gpio is to be set.

		A returned status of PI_SOME_PERMITTED indicates that the user is not allowed to write to one or more of the gpios.
		...
		$pi->set_bank_2(bindec("111110010000"))
		*/
 		return($this -> _pigpio_command($this->socket, $this->_PI_CMD_BS2, $bits, 0));	
	}	

	#error message if negative, or error code - result - if positive
	private function check_error($errCode){
		if($this->pigpioExceptions === True && $errCode<0){
			if(isset($this->_errors[$errCode])) throw new Exception("piGPIO failed: ".$this->_errors[$errCode]);
			else throw new Exception("piGPIO failed with unknown error code: ".$errCode);
		} else {
			return $errCode;
		}
	}

	private function warningHandler($errno, $errstr) { 
		//this manages stupid warning from socket functions
		throw new Exception("Socket error [".$errno."]: ".$errstr);
	}	

	#prints module version
	public function version(){
		return $this->version;
	}
	
	#closing socket and class
	public function __destruct() {
		if($this->pigpioExceptions === True){
			//need to redefine warning management
			set_error_handler(array($this, 'warningHandler'), E_WARNING);
			#disconnecting!
			socket_close($this->socket);
			restore_error_handler();
		} else {
			#disconnecting!
			socket_close($this->socket);
		}
	}
}

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

Re: New pigpio Python module

Thu Oct 01, 2015 9:06 pm

Massi wrote:i've noticed that in some cases the "correct" -3 is returned when using wrong gpio.
ie: set_mode, returns -41
read, returns -3
...
You only need permissions to alter a GPIO. So reading a GPIO, finding its mode, frequency etc. will give -3 for illegal GPIO numbers as the permissions check isn't done.

User avatar
penguinmanraspberry
Posts: 12
Joined: Fri Jul 05, 2013 10:31 am

Re: New pigpio Python module

Mon Oct 26, 2015 4:49 am

I would like to use pigpio.

However when trying to run any example or code snippet (using idle) I get

Can't connect to pigpio on localhost(8888)
and various other messages that make no sense to me.
etc

Are there instructions for setting up and using pigpio on the raspberry pi ?

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Mon Oct 26, 2015 6:26 am

you need to run pigpiod first
as reported by the help page:
Usage

This module uses the services of the C pigpio library. pigpio must be running on the Pi(s) whose gpios are to be manipulated.

The normal way to start pigpio is as a daemon (during system start).

sudo pigpiod

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Nov 03, 2015 11:28 am

joan,
am i wrong or in the python module some functions are not checking errors from the pigpio daemon?
in details:
- reading banks
- getting current tick
- getting hardware revision and pigpio version.

I can clearly understand the reason (since i'm facing it, LOL), i just wanted a confirmation..
In php integers are only signed, so my read_bank is giving a unknown pigpio error every time the first gpio of the bank is high :)

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

Re: New pigpio Python module

Tue Nov 03, 2015 12:39 pm

Massi wrote:joan,
am i wrong or in the python module some functions are not checking errors from the pigpio daemon?
in details:
- reading banks
- getting current tick
- getting hardware revision and pigpio version.

I can clearly understand the reason (since i'm facing it, LOL), i just wanted a confirmation..
In php integers are only signed, so my read_bank is giving a unknown pigpio error every time the first gpio of the bank is high :)
You are right. In effect those 5 return a 32 bit quantity which may be seen as a negative error code. They are treated as if they can't fail (which they can't).

In the Python module they are treated differently. If you search for "return _pigpio_command" you will see those functions. All the other functions use a negative number to indicate error and in the Python module use the form "return _u2i(_pigpio_command". The _u2i function checks for a negative number and raises an error.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Tue Nov 03, 2015 1:40 pm

joan wrote:You are right. In effect those 5 return a 32 bit quantity which may be seen as a negative error code. They are treated as if they can't fail (which they can't).

In the Python module they are treated differently. If you search for "return _pigpio_command" you will see those functions. All the other functions use a negative number to indicate error and in the Python module use the form "return _u2i(_pigpio_command". The _u2i function checks for a negative number and raises an error.
perfect, so i got it right.
I'll manage it in the same way in php..
thanks as always :)

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Wed Nov 18, 2015 9:34 pm

Joan,
sorry for using this topic for every question about pigpio :)

I have a little problem: sometimes i get a "I2C read failed" while reading from a i2c device.
I have some services making use of i2c bus (some sensors, for example) and if i stop those services (so i leave the bus free for other - SINGLE - operations) i never get the error.

So, the question: does pigpiod manage i2c requests sending them serially or is there the risk of having concurrent requests breaking each other?

Thanks

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

Re: New pigpio Python module

Wed Nov 18, 2015 9:52 pm

Massi wrote:Joan,
sorry for using this topic for every question about pigpio :)

I have a little problem: sometimes i get a "I2C read failed" while reading from a i2c device.
I have some services making use of i2c bus (some sensors, for example) and if i stop those services (so i leave the bus free for other - SINGLE - operations) i never get the error.

So, the question: does pigpiod manage i2c requests sending them serially or is there the risk of having concurrent requests breaking each other?

Thanks
Short answer is I don't know.

I have always assumed the I2C commands were atomic so couldn't clash with each other. Each time you get a handle by opening an I2C device you get a unique file descriptor dedicated to that device. I don't see that what I am doing within pigpio is any different to having several separate programs accessing I2C simultaneously. One assumes that is safe.

The fact that you are having problems indicate that there is a problem. I just need to know if this is a problem within pigpio or a general I2C driver problem.

All my I2C stuff has read devices in sequence within a single thread. I'll put together a test with separate threads. Not sure when though.

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

Re: New pigpio Python module

Thu Nov 19, 2015 1:43 pm

No problems are leaping out.

The following code starts three threads, each communicating with an I2C device in a busy loop.

I let it run for 5 minutes. Each thread averaged just under 60 I2C reads per second. I can't see any problems.

Code: Select all

#!/usr/bin/env python

import threading
import time
import struct
import sys

import pigpio

if sys.version > '3':
   buffer = memoryview

BUS=1

# Registers.

ITG_3205_WHO_AM_I    = 0x00 # R/W
ITG_3205_SMPLRT_DIV  = 0x15 # R/W
ITG_3205_DLPF_FS     = 0x16 # R/W
ITG_3205_INT_CFG     = 0x17 # R/W
ITG_3205_INT_STATUS  = 0x1A # R
ITG_3205_TEMP_OUT_H  = 0x1B # R
ITG_3205_TEMP_OUT_L  = 0x1C # R
ITG_3205_GYRO_XOUT_H = 0x1D # R
ITG_3205_GYRO_XOUT_L = 0x1E # R
ITG_3205_GYRO_YOUT_H = 0x1F # R
ITG_3205_GYRO_YOUT_L = 0x20 # R
ITG_3205_GYRO_ZOUT_H = 0x21 # R
ITG_3205_GYRO_ZOUT_L = 0x22 # R
ITG_3205_PWR_MGM     = 0x3E # R/W

# DLPF_FS

ITG_3205_FS_SEL_2000_DEG_SEC = 0x18

ITG_3205_DLPF_CFG_256_8 = 0x00
ITG_3205_DLPF_CFG_188_1 = 0x01
ITG_3205_DLPF_CFG_98_1  = 0x02
ITG_3205_DLPF_CFG_42_1  = 0x03
ITG_3205_DLPF_CFG_20_1  = 0x04
ITG_3205_DLPF_CFG_10_1  = 0x05
ITG_3205_DLPF_CFG_5_1   = 0x06

# INT_CFG

ITG_3205_IC_ACTL             = 0x80
ITG_3205_IC_OPEN             = 0x40
ITG_3205_IC_LATCH_INT_EN     = 0x20
ITG_3205_IC_INT_ANYRD_2CLEAR = 0x10
ITG_3205_IC_ITG_RDY_EN       = 0x04
ITG_3205_IC_RAW_RDY_EN       = 0x01

# INT_STATUS

ITG_3205_IS_ITG_RDY      = 0x04
ITG_3205_IS_RAW_DATA_RDY = 0x01

# PWR_MGM

ITG_3205_PM_H_RESET = 0x80
ITG_3205_PM_SLEEP   = 0x40
ITG_3205_PM_STBY_XG = 0x20
ITG_3205_PM_STBY_YG = 0x10
ITG_3205_PM_STBY_ZG = 0x08

ITG_3205_PM_CLK_SEL_INTERNAL  = 0x00
ITG_3205_PM_CLK_SEL_X_GYRO    = 0x01
ITG_3205_PM_CLK_SEL_Y_GYRO    = 0x02
ITG_3205_PM_CLK_SEL_Z_GYRO    = 0x03
ITG_3205_PM_CLK_SEL_EXT_32768 = 0x04
ITG_3205_PM_CLK_SEL_EXT_192   = 0x05

ADXL345_I2C_ADDR=0x53

HMC5883L_I2C_ADDR=0x1E

ITG_3205_I2C_ADDR=0x68

def HMC5883L():
   global pi, run

   h = pi.i2c_open(BUS, HMC5883L_I2C_ADDR)

   if h >= 0: # Connected OK?

      # Initialise HMC5883L.
      pi.i2c_write_byte_data(h, 0x00, 0xF8)  # CRA 75Hz.
      pi.i2c_write_byte_data(h, 0x02, 0x00)  # Mode continuous reads.

      read = 0

      while run:

         # 0x3 = X MSB, 0x4 = X LSB
         # 0x5 = Y MSB, 0x6 = Y LSB
         # 0x7 = Z MSB, 0x8 = Z LSB

         # > = big endian

         (s, b) = pi.i2c_read_i2c_block_data(h, 0x03, 6)

         if s >= 0:
            (x, y, z) = struct.unpack('>3h', buffer(b))
            print("HMC5883L: {} {} {}".format(int(x/5), int(y/5), int(z/5)))
            read += 1

      pi.i2c_close(h)

   time.sleep(1)

   print("HMC5883L", read, read/RUNTIME)

def ADXL345():
   global pi, run
   h = pi.i2c_open(BUS, ADXL345_I2C_ADDR)

   if h >= 0: # Connected OK?

      # Initialise ADXL345.
      pi.i2c_write_byte_data(h, 0x2d, 0)  # POWER_CTL reset.
      pi.i2c_write_byte_data(h, 0x2d, 8)  # POWER_CTL measure.
      pi.i2c_write_byte_data(h, 0x31, 0)  # DATA_FORMAT reset.
      pi.i2c_write_byte_data(h, 0x31, 11) # DATA_FORMAT full res +/- 16g.

      read = 0

      while run:

         # 0x32 = X LSB, 0x33 = X MSB
         # 0x34 = Y LSB, 0x35 = Y MSB
         # 0x36 = Z LSB, 0x37 = Z MSB

         # < = little endian

         (s, b) = pi.i2c_read_i2c_block_data(h, 0x32, 6)

         if s >= 0:
            (x, y, z) = struct.unpack('<3h', buffer(b))
            print("ADXL345: {} {} {}".format(int(x/5), int(y/5), int(z/5)))
            read += 1

   time.sleep(1)

   print("ADXL345", read, read/RUNTIME)

def ITG_3205():
   global pi, run

   h = pi.i2c_open(BUS, ITG_3205_I2C_ADDR)

   if h >= 0: # Connected OK?

      # Initialise ITG_3205.

      pi.i2c_write_byte_data(h, ITG_3205_PWR_MGM, ITG_3205_PM_CLK_SEL_X_GYRO)
      #                         0x3E              0x01

      pi.i2c_write_byte_data(h, ITG_3205_SMPLRT_DIV, 0x07)
      #                         0x15                 0x07

      pi.i2c_write_byte_data(h, ITG_3205_DLPF_FS,
         ITG_3205_FS_SEL_2000_DEG_SEC | ITG_3205_DLPF_CFG_188_1)
      # 0x16  0x18| 0x01

      pi.i2c_write_byte_data(h, ITG_3205_INT_CFG, 0x00)
      # 0x17 0x00

      read = 0

      while run:

         # 0x1B = T MSB, 0x1C = T LSB
         # 0x1D = X MSB, 0x1E = X LSB
         # 0x1F = Y MSB, 0x20 = Y LSB
         # 0x20 = Z MSB, 0x22 = Z LSB

         # > = big endian

         (s, b) = pi.i2c_read_i2c_block_data(h, ITG_3205_TEMP_OUT_H, 8)

         if s >= 0:
            (t, x, y, z) = struct.unpack('>4h', buffer(b))
            t = 35 + ((t + 13200) / 280.0)
            print("ITG_3205: {} {} {} {}".format(int(t/5), int(x/5), int(y/5), int(z/5)))
            read += 1

      pi.i2c_close(h)

   time.sleep(1)

   print("ITG3205", read, read/RUNTIME)

RUNTIME=300

run = True

pi=pigpio.pi() # open local Pi

t1 = threading.Thread(name='ITG_3205', target=ITG_3205)
t1.start()

t2 = threading.Thread(name='ADXL345', target=ADXL345)
t2.start()

t3 = threading.Thread(name='HMC5883L', target=HMC5883L)
t3.start()

time.sleep(RUNTIME)

run = False

time.sleep(2)

pi.stop()
I sorted the log data and show the unique records below.

Code: Select all

      1 ADXL345: 53 0 -6
     41 ADXL345: 53 0 -8
      1 ADXL345: 53 1 -7
     92 ADXL345: 53 1 -8
     12 ADXL345: 53 1 -9
      1 ADXL345: 53 2 -8
     91 ADXL345: 54 0 -7
  10236 ADXL345: 54 0 -8
     51 ADXL345: 54 0 -9
      3 ADXL345: 54 1 -6
     90 ADXL345: 54 1 -7
   6834 ADXL345: 54 1 -8
     59 ADXL345: 54 1 -9
      4 ADXL345: 55 0 -8
      4 ADXL345: 55 1 -8
      2 HMC5883L: -108 64 -11
    439 HMC5883L: -108 64 -12
      8 HMC5883L: -108 65 -11
  16843 HMC5883L: -108 65 -12
    325 HMC5883L: -108 66 -12
      3 HMC5883L: -109 64 -12
    114 HMC5883L: -109 65 -12
      2 HMC5883L: -109 66 -12
    619 ITG_3205: 4 0 0 -2
    854 ITG_3205: 4 0 0 -3
      2 ITG_3205: 4 0 1 -2
   7148 ITG_3205: 4 -1 0 -2
   9143 ITG_3205: 4 -1 0 -3
      7 ITG_3205: 4 -1 1 -2
      1 ITG_3205: 4 -1 -1 -3
      6 ITG_3205: 4 -1 1 -3
      1 ('ADXL345', 17520, 58)
      1 ('HMC5883L', 17736, 59)
      1 ('ITG3205', 17780, 59)
Would you expect the error in a 5 minute run?

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: New pigpio Python module

Thu Nov 19, 2015 2:08 pm

yes, far far sooner.
So i'm lost :) need to look at it deeper..
Thanks joan

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

Re: New pigpio Python module

Thu Nov 19, 2015 7:23 pm

Massi wrote:yes, far far sooner.
So i'm lost :) need to look at it deeper..
Thanks joan
If you can put together a small program which shows the problem I'll have a look.

Return to “Python”