/*
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>
*/

/* PIGPIOD_IF2_VERSION 2 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <netdb.h>
#include <pthread.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <sys/select.h>

#include <arpa/inet.h>

#include "pigpio.h"
#include "command.h"

#include "pigpiod_if2.h"

#define PI_MAX_REPORTS_PER_READ 4096

#define STACK_SIZE (256*1024)

#define MAX_PI 32

typedef void (*CBF_t) ();

struct callback_s
{

   int id;
   int pi;
   int gpio;
   int edge;
   CBF_t f;
   void * user;
   int ex;
   callback_t *prev;
   callback_t *next;
};

/* GLOBALS ---------------------------------------------------------------- */

static int             gPiInUse     [MAX_PI];

static int             gPigCommand  [MAX_PI];
static int             gPigHandle   [MAX_PI];
static int             gPigNotify   [MAX_PI];

static uint32_t        gNotifyBits  [MAX_PI];
static uint32_t        gLastLevel   [MAX_PI];

static pthread_t       *gPthNotify  [MAX_PI];

static pthread_mutex_t gCmdMutex    [MAX_PI];
static int             gCancelState [MAX_PI];

static callback_t *gCallBackFirst = 0;
static callback_t *gCallBackLast  = 0;

/* PRIVATE ---------------------------------------------------------------- */

static void _pml(int pi)
{
   int cancelState;

   pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancelState);
   pthread_mutex_lock(&gCmdMutex[pi]);
   gCancelState[pi] = cancelState;
}

static void _pmu(int pi)
{
   int cancelState;

   cancelState = gCancelState[pi];
   pthread_mutex_unlock(&gCmdMutex[pi]);
   pthread_setcancelstate(cancelState, NULL);
}

static int pigpio_command(int pi, int command, int p1, int p2, int rl)
{
   cmdCmd_t cmd;

   if ((pi < 0) || (pi >= MAX_PI) || !gPiInUse[pi])
      return pigif_unconnected_pi;

   cmd.cmd = command;
   cmd.p1  = p1;
   cmd.p2  = p2;
   cmd.res = 0;

   _pml(pi);

   if (send(gPigCommand[pi], &cmd, sizeof(cmd), 0) != sizeof(cmd))
   {
      _pmu(pi);
      return pigif_bad_send;
   }

   if (recv(gPigCommand[pi], &cmd, sizeof(cmd), MSG_WAITALL) != sizeof(cmd))
   {
      _pmu(pi);
      return pigif_bad_recv;
   }

   if (rl) _pmu(pi);

   return cmd.res;
}

static int pigpio_notify(int pi)
{
   cmdCmd_t cmd;

   if ((pi < 0) || (pi >= MAX_PI) || !gPiInUse[pi])
      return pigif_unconnected_pi;

   cmd.cmd = PI_CMD_NOIB;
   cmd.p1  = 0;
   cmd.p2  = 0;
   cmd.res = 0;

   _pml(pi);

   if (send(gPigNotify[pi], &cmd, sizeof(cmd), 0) != sizeof(cmd))
   {
      _pmu(pi);
      return pigif_bad_send;
   }

   if (recv(gPigNotify[pi], &cmd, sizeof(cmd), MSG_WAITALL) != sizeof(cmd))
   {
      _pmu(pi);
      return pigif_bad_recv;
   }

   _pmu(pi);

   return cmd.res;
}

static int pigpio_command_ext
   (int pi, int command, int p1, int p2, int p3,
    int extents, gpioExtent_t *ext, int rl)
{
   int i;
   cmdCmd_t cmd;

   if ((pi < 0) || (pi >= MAX_PI) || !gPiInUse[pi])
      return pigif_unconnected_pi;

   cmd.cmd = command;
   cmd.p1  = p1;
   cmd.p2  = p2;
   cmd.p3  = p3;

   _pml(pi);

   if (send(gPigCommand[pi], &cmd, sizeof(cmd), 0) != sizeof(cmd))
   {
      _pmu(pi);
      return pigif_bad_send;
   }

   for (i=0; i<extents; i++)
   {
      if (send(gPigCommand[pi], ext[i].ptr, ext[i].size, 0) != ext[i].size)
      {
         _pmu(pi);
         return pigif_bad_send;
      }
   }

   if (recv(gPigCommand[pi], &cmd, sizeof(cmd), MSG_WAITALL) != sizeof(cmd))
   {
      _pmu(pi);
      return pigif_bad_recv;
   }
   if (rl) _pmu(pi);

   return cmd.res;
}

static int pigpioOpenSocket(char *addr, char *port)
{
   int sock, err, opt;
   struct addrinfo hints, *res, *rp;
   const char *addrStr, *portStr;

   if (!addr)
   {
      addrStr = getenv(PI_ENVADDR);

      if ((!addrStr) || (!strlen(addrStr)))
      {
         addrStr = PI_DEFAULT_SOCKET_ADDR_STR;
      }
   }
   else addrStr = addr;

   if (!port)
   {
      portStr = getenv(PI_ENVPORT);

      if ((!portStr) || (!strlen(portStr)))
      {
         portStr = PI_DEFAULT_SOCKET_PORT_STR;
      }
   }
   else portStr = port;

   memset (&hints, 0, sizeof (hints));

   hints.ai_family   = PF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags   |= AI_CANONNAME;

   err = getaddrinfo (addrStr, portStr, &hints, &res);

   if (err) return pigif_bad_getaddrinfo;

   for (rp=res; rp!=NULL; rp=rp->ai_next)
   {
      sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

      if (sock == -1) continue;

      /* Disable the Nagle algorithm. */
      opt = 1;
      setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(int));

      if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) break;
   }

   freeaddrinfo(res);

   if (rp == NULL) return pigif_bad_connect;

   return sock;
}

static void dispatch_notification(int pi, gpioReport_t *r)
{
   callback_t *p;
   uint32_t changed;
   int l, g;

/*
   printf("s=%4x f=%4x t=%10u l=%8x\n",
      r->seqno, r->flags, r->tick, r->level);
*/

   if (r->flags == 0)
   {
      changed = (r->level ^ gLastLevel[pi]) & gNotifyBits[pi];

      gLastLevel[pi] = r->level;

      p = gCallBackFirst;

      while (p)
      {
         if (((p->pi) == pi) && (changed & (1<<(p->gpio))))
         {
            if ((r->level) & (1<<(p->gpio))) l = 1; else l = 0;
            if ((p->edge) ^ l)
            {
               if (p->ex) (p->f)(pi, p->gpio, l, r->tick, p->user);
               else       (p->f)(pi, p->gpio, l, r->tick);
            }
         }
         p = p->next;
      }
   }
   else
   {
      g = (r->flags) & 31;

      p = gCallBackFirst;

      while (p)
      {
         if (((p->pi) == pi) && ((p->gpio) == g))
         {
            if (p->ex) (p->f)(pi, g, PI_TIMEOUT, r->tick, p->user);
            else       (p->f)(pi, g, PI_TIMEOUT, r->tick);
         }
         p = p->next;
      }
   }
}

static void *pthNotifyThread(void *x)
{
   static int got = 0;
   int pi;
   int bytes, r;
   gpioReport_t report[PI_MAX_REPORTS_PER_READ];

   pi = *((int*)x);
   free(x); /* memory allocated in pigpio_start */

   while (1)
   {
      bytes = read(gPigNotify[pi], (char*)&report+got, sizeof(report)-got);

      if (bytes > 0) got += bytes;
      else break;

      r = 0;

      while (got >= sizeof(gpioReport_t))
      {
         dispatch_notification(pi, &report[r]);

         r++;

         got -= sizeof(gpioReport_t);
      }

      /* copy any partial report to start of array */
      
      if (got && r) report[0] = report[r];
   }

   fprintf(stderr, "notify thread for pi %d broke with read error %d\n",
      pi, bytes);

   while (1) sleep(1);

   return NULL;
}

static void findNotifyBits(int pi)
{
   callback_t *p;
   uint32_t bits = 0;

   p = gCallBackFirst;

   while (p)
   {
      if (p->pi == pi) bits |= (1<<(p->gpio));
      p = p->next;
   }

   if (bits != gNotifyBits[pi])
   {
      gNotifyBits[pi] = bits;
      pigpio_command(pi, PI_CMD_NB, gPigHandle[pi], gNotifyBits[pi], 1);
   }
}

static void _wfe(
   int pi, unsigned user_gpio, unsigned level, uint32_t tick, void *user)
{
   *(int *)user = 1;
}

static int intCallback(
   int pi, unsigned user_gpio, unsigned edge, void *f, void *user, int ex)
{
   static int id = 0;
   callback_t *p;

   if ((user_gpio >=0) && (user_gpio < 32) && (edge >=0) && (edge <= 2) && f)
   {
      /* prevent duplicates */

      p = gCallBackFirst;

      while (p)
      {
         if ((p->pi   == pi)        &&
             (p->gpio == user_gpio) &&
             (p->edge == edge)      &&
             (p->f    == f))
         {
            return pigif_duplicate_callback;
         }
         p = p->next;
      }

      p = malloc(sizeof(callback_t));

      if (p)
      {
         if (!gCallBackFirst) gCallBackFirst = p;

         p->id = id++;
         p->pi = pi;
         p->gpio = user_gpio;
         p->edge = edge;
         p->f = f;
         p->user = user;
         p->ex = ex;
         p->next = 0;
         p->prev = gCallBackLast;

         if (p->prev) (p->prev)->next = p;
         gCallBackLast = p;

         findNotifyBits(pi);

         return p->id;
      }

      return pigif_bad_malloc;
   }

   return pigif_bad_callback;
}

static int recvMax(int pi, void *buf, int bufsize, int sent)
{
   uint8_t scratch[4096];
   int remaining, fetch, count;

   if (sent < bufsize) count = sent; else count = bufsize;

   if (count) recv(gPigCommand[pi], buf, count, MSG_WAITALL);

   remaining = sent - count;

   while (remaining)
   {
      fetch = remaining;
      if (fetch > sizeof(scratch)) fetch = sizeof(scratch);
      recv(gPigCommand[pi], scratch, fetch, MSG_WAITALL);
      remaining -= fetch;
   }

   return count;
}

/* PUBLIC ----------------------------------------------------------------- */

double time_time(void)
{
   struct timeval tv;
   double t;

   gettimeofday(&tv, 0);

   t = (double)tv.tv_sec + ((double)tv.tv_usec / 1E6);

   return t;
}

void time_sleep(double seconds)
{
   struct timespec ts, rem;

   if (seconds > 0.0)
   {
      ts.tv_sec = seconds;
      ts.tv_nsec = (seconds-(double)ts.tv_sec) * 1E9;

      while (clock_nanosleep(CLOCK_REALTIME, 0, &ts, &rem))
      {
         /* copy remaining time to ts */
         ts.tv_sec  = rem.tv_sec;
         ts.tv_nsec = rem.tv_nsec;
      }
   }
}

char *pigpio_error(int errnum)
{
   if (errnum > -1000) return cmdErrStr(errnum);
   else
   {
      switch(errnum)
      {
         case pigif_bad_send:
            return "failed to send to pigpiod";
         case pigif_bad_recv:
            return "failed to receive from pigpiod";
         case pigif_bad_getaddrinfo:
            return "failed to find address of pigpiod";
         case pigif_bad_connect:
            return "failed to connect to pigpiod";
         case pigif_bad_socket:
            return "failed to create socket";
         case pigif_bad_noib:
            return "failed to open notification in band";
         case pigif_duplicate_callback:
            return "identical callback exists";
         case pigif_bad_malloc:
            return "failed to malloc";
         case pigif_bad_callback:
            return "bad callback parameter";
         case pigif_notify_failed:
            return "failed to create notification thread";
         case pigif_callback_not_found:
            return "callback not found";
         case pigif_unconnected_pi:
            return "not connected to Pi";
         case pigif_too_many_pis:
            return "too many connected Pis";

         default:
            return "unknown error";
      }
   }
}

unsigned pigpiod_if_version(void)
{
   return PIGPIOD_IF2_VERSION;
}

pthread_t *start_thread(gpioThreadFunc_t thread_func, void *userdata)
{
   pthread_t *pth;
   pthread_attr_t pthAttr;

   pth = malloc(sizeof(pthread_t));

   if (pth)
   {
      if (pthread_attr_init(&pthAttr))
      {
         perror("pthread_attr_init failed");
         free(pth);
         return NULL;
      }

      if (pthread_attr_setstacksize(&pthAttr, STACK_SIZE))
      {
         perror("pthread_attr_setstacksize failed");
         free(pth);
         return NULL;
      }

      if (pthread_create(pth, &pthAttr, thread_func, userdata))
      {
         perror("pthread_create socket failed");
         free(pth);
         return NULL;
      }
   }
   return pth;
}

void stop_thread(pthread_t *pth)
{
   if (pth)
   {
      pthread_cancel(*pth);
      pthread_join(*pth, NULL);
      free(pth);
   }
}

int pigpio_start(char *addrStr, char *portStr)
{
   int pi;
   int *userdata;

   for (pi=0; pi<MAX_PI; pi++)
   {
      if (!gPiInUse[pi]) break;
   }

   if (pi >= MAX_PI) return pigif_too_many_pis;

   gPiInUse[pi] = 1;

   pthread_mutex_init(&gCmdMutex[pi], NULL);

   gPigCommand[pi] = pigpioOpenSocket(addrStr, portStr);

   if (gPigCommand[pi] >= 0)
   {
      gPigNotify[pi] = pigpioOpenSocket(addrStr, portStr);

      if (gPigNotify[pi] >= 0)
      {
         gPigHandle[pi] = pigpio_notify(pi);

         if (gPigHandle[pi] < 0) return pigif_bad_noib;
         else
         {
            gLastLevel[pi] = read_bank_1(pi);

            /* must be freed by pthNotifyThread */
            userdata = malloc(sizeof(*userdata));
            *userdata = pi;

            gPthNotify[pi] = start_thread(pthNotifyThread, userdata);

            if (gPthNotify[pi]) return pi;
            else                return pigif_notify_failed;

         }
      }
      else return gPigNotify[pi];
   }
   else return gPigCommand[pi];
}

void pigpio_stop(int pi)
{
   if ((pi < 0) || (pi >= MAX_PI) || !gPiInUse[pi]) return;

   if (gPthNotify[pi])
   {
      stop_thread(gPthNotify[pi]);
      gPthNotify[pi] = 0;
   }

   if (gPigCommand[pi] >= 0)
   {
      if (gPigHandle[pi] >= 0)
      {
         pigpio_command(pi, PI_CMD_NC, gPigHandle[pi], 0, 1);
         gPigHandle[pi] = -1;
      }

      close(gPigCommand[pi]);
      gPigCommand[pi] = -1;
   }

   if (gPigNotify[pi] >= 0)
   {
      close(gPigNotify[pi]);
      gPigNotify[pi] = -1;
   }

   gPiInUse[pi] = 0;
}

int set_mode(int pi, unsigned gpio, unsigned mode)
   {return pigpio_command(pi, PI_CMD_MODES, gpio, mode, 1);}

int get_mode(int pi, unsigned gpio)
   {return pigpio_command(pi, PI_CMD_MODEG, gpio, 0, 1);}

int set_pull_up_down(int pi, unsigned gpio, unsigned pud)
   {return pigpio_command(pi, PI_CMD_PUD, gpio, pud, 1);}

int gpio_read(int pi, unsigned gpio)
   {return pigpio_command(pi, PI_CMD_READ, gpio, 0, 1);}

int gpio_write(int pi, unsigned gpio, unsigned level)
   {return pigpio_command(pi, PI_CMD_WRITE, gpio, level, 1);}

int set_PWM_dutycycle(int pi, unsigned user_gpio, unsigned dutycycle)
   {return pigpio_command(pi, PI_CMD_PWM, user_gpio, dutycycle, 1);}

int get_PWM_dutycycle(int pi, unsigned user_gpio)
   {return pigpio_command(pi, PI_CMD_GDC, user_gpio, 0, 1);}

int set_PWM_range(int pi, unsigned user_gpio, unsigned range)
   {return pigpio_command(pi, PI_CMD_PRS, user_gpio, range, 1);}

int get_PWM_range(int pi, unsigned user_gpio)
   {return pigpio_command(pi, PI_CMD_PRG, user_gpio, 0, 1);}

int get_PWM_real_range(int pi, unsigned user_gpio)
   {return pigpio_command(pi, PI_CMD_PRRG, user_gpio, 0, 1);}

int set_PWM_frequency(int pi, unsigned user_gpio, unsigned frequency)
   {return pigpio_command(pi, PI_CMD_PFS, user_gpio, frequency, 1);}

int get_PWM_frequency(int pi, unsigned user_gpio)
   {return pigpio_command(pi, PI_CMD_PFG, user_gpio, 0, 1);}

int set_servo_pulsewidth(int pi, unsigned user_gpio, unsigned pulsewidth)
   {return pigpio_command(pi, PI_CMD_SERVO, user_gpio, pulsewidth, 1);}

int get_servo_pulsewidth(int pi, unsigned user_gpio)
   {return pigpio_command(pi, PI_CMD_GPW, user_gpio, 0, 1);}

int notify_open(int pi)
   {return pigpio_command(pi, PI_CMD_NO, 0, 0, 1);}

int notify_begin(int pi, unsigned handle, uint32_t bits)
   {return pigpio_command(pi, PI_CMD_NB, handle, bits, 1);}

int notify_pause(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_NB, handle, 0, 1);}

int notify_close(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_NC, handle, 0, 1);}

int set_watchdog(int pi, unsigned user_gpio, unsigned timeout)
   {return pigpio_command(pi, PI_CMD_WDOG, user_gpio, timeout, 1);}

uint32_t read_bank_1(int pi)
   {return pigpio_command(pi, PI_CMD_BR1, 0, 0, 1);}

uint32_t read_bank_2(int pi)
   {return pigpio_command(pi, PI_CMD_BR2, 0, 0, 1);}

int clear_bank_1(int pi, uint32_t levels)
   {return pigpio_command(pi, PI_CMD_BC1, levels, 0, 1);}

int clear_bank_2(int pi, uint32_t levels)
   {return pigpio_command(pi, PI_CMD_BC2, levels, 0, 1);}

int set_bank_1(int pi, uint32_t levels)
   {return pigpio_command(pi, PI_CMD_BS1, levels, 0, 1);}

int set_bank_2(int pi, uint32_t levels)
   {return pigpio_command(pi, PI_CMD_BS2, levels, 0, 1);}

int hardware_clock(int pi, unsigned gpio, unsigned frequency)
   {return pigpio_command(pi, PI_CMD_HC, gpio, frequency, 1);}

int hardware_PWM(int pi, unsigned gpio, unsigned frequency, uint32_t dutycycle)
{
   gpioExtent_t ext[1];
   
   /*
   p1=gpio
   p2=frequency
   p3=4
   ## extension ##
   uint32_t dutycycle
   */

   ext[0].size = sizeof(dutycycle);
   ext[0].ptr = &dutycycle;

   return pigpio_command_ext(
      pi, PI_CMD_HP, gpio, frequency, sizeof(dutycycle), 1, ext, 1);
}

uint32_t get_current_tick(int pi)
   {return pigpio_command(pi, PI_CMD_TICK, 0, 0, 1);}

uint32_t get_hardware_revision(int pi)
   {return pigpio_command(pi, PI_CMD_HWVER, 0, 0, 1);}

uint32_t get_pigpio_version(int pi)
   {return pigpio_command(pi, PI_CMD_PIGPV, 0, 0, 1);}

int wave_clear(int pi)
   {return pigpio_command(pi, PI_CMD_WVCLR, 0, 0, 1);}

int wave_add_new(int pi)
   {return pigpio_command(pi, PI_CMD_WVNEW, 0, 0, 1);}

int wave_add_generic(int pi, unsigned numPulses, gpioPulse_t *pulses)
{
   gpioExtent_t ext[1];
   
   /*
   p1=0
   p2=0
   p3=pulses*sizeof(gpioPulse_t)
   ## extension ##
   gpioPulse_t[] pulses
   */

   if (!numPulses) return 0;

   ext[0].size = numPulses * sizeof(gpioPulse_t);
   ext[0].ptr = pulses;

   return pigpio_command_ext(
      pi, PI_CMD_WVAG, 0, 0, ext[0].size, 1, ext, 1);
}

int wave_add_serial(
   int pi, unsigned user_gpio, unsigned baud, uint32_t databits,
   uint32_t stophalfbits, uint32_t offset,  unsigned numChar, char *str)
{
   uint8_t buf[12];
   gpioExtent_t ext[2];

   /*
   p1=user_gpio
   p2=baud
   p3=len+12
   ## extension ##
   uint32_t databits
   uint32_t stophalfbits
   uint32_t offset
   char[len] str
   */

   if (!numChar) return 0;

   memcpy(buf, &databits, 4);
   memcpy(buf+4, &stophalfbits, 4);
   memcpy(buf+8, &offset, 4);

   ext[0].size = sizeof(buf);
   ext[0].ptr = buf;

   ext[1].size = numChar;
   ext[1].ptr = str;

   return pigpio_command_ext(pi, PI_CMD_WVAS,
      user_gpio, baud, numChar+sizeof(buf), 2, ext, 1);
}

int wave_create(int pi)
   {return pigpio_command(pi, PI_CMD_WVCRE, 0, 0, 1);}

int wave_delete(int pi, unsigned wave_id)
   {return pigpio_command(pi, PI_CMD_WVDEL, wave_id, 0, 1);}

int wave_tx_start(int pi) /* DEPRECATED */
   {return pigpio_command(pi, PI_CMD_WVGO, 0, 0, 1);}

int wave_tx_repeat(int pi) /* DEPRECATED */
   {return pigpio_command(pi, PI_CMD_WVGOR, 0, 0, 1);}

int wave_send_once(int pi, unsigned wave_id)
   {return pigpio_command(pi, PI_CMD_WVTX, wave_id, 0, 1);}

int wave_send_repeat(int pi, unsigned wave_id)
   {return pigpio_command(pi, PI_CMD_WVTXR, wave_id, 0, 1);}

int wave_chain(int pi, char *buf, unsigned bufSize)
{
   gpioExtent_t ext[1];

   /*
   p1=0
   p2=0
   p3=bufSize
   ## extension ##
   char buf[bufSize]
   */

   ext[0].size = bufSize;
   ext[0].ptr = buf;

   return pigpio_command_ext
      (pi, PI_CMD_WVCHA, 0, 0, bufSize, 1, ext, 1);
}

int wave_tx_busy(int pi)
   {return pigpio_command(pi, PI_CMD_WVBSY, 0, 0, 1);}

int wave_tx_stop(int pi)
   {return pigpio_command(pi, PI_CMD_WVHLT, 0, 0, 1);}

int wave_get_micros(int pi)
   {return pigpio_command(pi, PI_CMD_WVSM, 0, 0, 1);}

int wave_get_high_micros(int pi)
   {return pigpio_command(pi, PI_CMD_WVSM, 1, 0, 1);}

int wave_get_max_micros(int pi)
   {return pigpio_command(pi, PI_CMD_WVSM, 2, 0, 1);}

int wave_get_pulses(int pi)
   {return pigpio_command(pi, PI_CMD_WVSP, 0, 0, 1);}

int wave_get_high_pulses(int pi)
   {return pigpio_command(pi, PI_CMD_WVSP, 1, 0, 1);}

int wave_get_max_pulses(int pi)
   {return pigpio_command(pi, PI_CMD_WVSP, 2, 0, 1);}

int wave_get_cbs(int pi)
   {return pigpio_command(pi, PI_CMD_WVSC, 0, 0, 1);}

int wave_get_high_cbs(int pi)
   {return pigpio_command(pi, PI_CMD_WVSC, 1, 0, 1);}

int wave_get_max_cbs(int pi)
   {return pigpio_command(pi, PI_CMD_WVSC, 2, 0, 1);}

int gpio_trigger(int pi, unsigned user_gpio, unsigned pulseLen, uint32_t level)
{
   gpioExtent_t ext[1];
   
   /*
   p1=user_gpio
   p2=pulseLen 
   p3=4
   ## extension ##
   unsigned level
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &level;

   return pigpio_command_ext(
      pi, PI_CMD_TRIG, user_gpio, pulseLen, 4, 1, ext, 1);
}

int set_glitch_filter(int pi, unsigned user_gpio, unsigned steady)
   {return pigpio_command(pi, PI_CMD_FG, user_gpio, steady, 1);}

int set_noise_filter(int pi, unsigned user_gpio, unsigned steady, unsigned active)
{
   gpioExtent_t ext[1];
   
   /*
   p1=user_gpio
   p2=steady
   p3=4
   ## extension ##
   unsigned active
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &active;

   return pigpio_command_ext(
      pi, PI_CMD_FN, user_gpio, steady, 4, 1, ext, 1);
}

int store_script(int pi, char *script)
{
   unsigned len;
   gpioExtent_t ext[1];

   /*
   p1=0
   p2=0
   p3=len
   ## extension ##
   char[len] script
   */

   len = strlen(script);

   if (!len) return 0;

   ext[0].size = len;
   ext[0].ptr = script;

   return pigpio_command_ext(pi, PI_CMD_PROC, 0, 0, len, 1, ext, 1);
}

int run_script(int pi, unsigned script_id, unsigned numPar, uint32_t *param)
{
   gpioExtent_t ext[1];

   /*
   p1=script id
   p2=0
   p3=numPar * 4
   ## extension ##
   uint32_t[numPar] pars
   */

   ext[0].size = 4 * numPar;
   ext[0].ptr = param;

   return pigpio_command_ext
      (pi, PI_CMD_PROCR, script_id, 0, numPar*4, 1, ext, 1);
}

int script_status(int pi, unsigned script_id, uint32_t *param)
{
   int status;
   uint32_t p[PI_MAX_SCRIPT_PARAMS+1]; /* space for script status */

   status = pigpio_command(pi, PI_CMD_PROCP, script_id, 0, 0);

   if (status > 0)
   {
      recvMax(pi, p, sizeof(p), status);
      status = p[0];
      if (param) memcpy(param, p+1, sizeof(p)-4);
   }

   _pmu(pi);

   return status;
}

int stop_script(int pi, unsigned script_id)
   {return pigpio_command(pi, PI_CMD_PROCS, script_id, 0, 1);}

int delete_script(int pi, unsigned script_id)
   {return pigpio_command(pi, PI_CMD_PROCD, script_id, 0, 1);}

int bb_serial_read_open(int pi, unsigned user_gpio, unsigned baud, uint32_t bbBits)
{
   gpioExtent_t ext[1];
   
   /*
   p1=user_gpio
   p2=baud
   p3=4
   ## extension ##
   unsigned bbBits
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &bbBits;

   return pigpio_command_ext(
      pi, PI_CMD_SLRO, user_gpio, baud, 4, 1, ext, 1);
}

int bb_serial_read(int pi, unsigned user_gpio, void *buf, size_t bufSize)
{
   int bytes;

   bytes = pigpio_command(pi, PI_CMD_SLR, user_gpio, bufSize, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, bufSize, bytes);
   }

   _pmu(pi);

   return bytes;
}

int bb_serial_read_close(int pi, unsigned user_gpio)
   {return pigpio_command(pi, PI_CMD_SLRC, user_gpio, 0, 1);}

int bb_serial_invert(int pi, unsigned user_gpio, unsigned invert)
   {return pigpio_command(pi, PI_CMD_SLRI, user_gpio, invert, 1);}

int i2c_open(int pi, unsigned i2c_bus, unsigned i2c_addr, uint32_t i2c_flags)
{
   gpioExtent_t ext[1];

   /*
   p1=i2c_bus
   p2=i2c_addr
   p3=4
   ## extension ##
   uint32_t i2c_flags
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &i2c_flags;

   return pigpio_command_ext
      (pi, PI_CMD_I2CO, i2c_bus, i2c_addr, 4, 1, ext, 1);
}

int i2c_close(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_I2CC, handle, 0, 1);}

int i2c_write_quick(int pi, unsigned handle, unsigned bit)
   {return pigpio_command(pi, PI_CMD_I2CWQ, handle, bit, 1);}

int i2c_write_byte(int pi, unsigned handle, unsigned val)
   {return pigpio_command(pi, PI_CMD_I2CWS, handle, val, 1);}

int i2c_read_byte(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_I2CRS, handle, 0, 1);}

int i2c_write_byte_data(int pi, unsigned handle, unsigned reg, uint32_t val)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=4
   ## extension ##
   uint32_t val
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &val;

   return pigpio_command_ext
      (pi, PI_CMD_I2CWB, handle, reg, 4, 1, ext, 1);
}

int i2c_write_word_data(int pi, unsigned handle, unsigned reg, uint32_t val)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=4
   ## extension ##
   uint32_t val
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &val;

   return pigpio_command_ext
      (pi, PI_CMD_I2CWW, handle, reg, 4, 1, ext, 1);
}

int i2c_read_byte_data(int pi, unsigned handle, unsigned reg)
   {return pigpio_command(pi, PI_CMD_I2CRB, handle, reg, 1);}

int i2c_read_word_data(int pi, unsigned handle, unsigned reg)
   {return pigpio_command(pi, PI_CMD_I2CRW, handle, reg, 1);}

int i2c_process_call(int pi, unsigned handle, unsigned reg, uint32_t val)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=4
   ## extension ##
   uint32_t val
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &val;

   return pigpio_command_ext
      (pi, PI_CMD_I2CPK, handle, reg, 4, 1, ext, 1);
}

int i2c_write_block_data(
   int pi, unsigned handle, unsigned reg, char *buf, unsigned count)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = buf;

   return pigpio_command_ext
      (pi, PI_CMD_I2CWK, handle, reg, count, 1, ext, 1);
}

int i2c_read_block_data(int pi, unsigned handle, unsigned reg, char *buf)
{
   int bytes;

   bytes = pigpio_command(pi, PI_CMD_I2CRK, handle, reg, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, 32, bytes);
   }

   _pmu(pi);

   return bytes;
}

int i2c_block_process_call(
   int pi, unsigned handle, unsigned reg, char *buf, unsigned count)
{
   int bytes;
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = buf;

   bytes = pigpio_command_ext
      (pi, PI_CMD_I2CPK, handle, reg, count, 1, ext, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, 32, bytes);
   }

   _pmu(pi);

   return bytes;
}

int i2c_read_i2c_block_data(
   int pi, unsigned handle, unsigned reg, char *buf, uint32_t count)
{
   int bytes;
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=4
   ## extension ##
   uint32_t count
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &count;

   bytes = pigpio_command_ext
      (pi, PI_CMD_I2CRI, handle, reg, 4, 1, ext, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, count, bytes);
   }

   _pmu(pi);

   return bytes;
}


int i2c_write_i2c_block_data(
   int pi, unsigned handle, unsigned reg, char *buf, unsigned count)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=reg
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = buf;

   return pigpio_command_ext
      (pi, PI_CMD_I2CWI, handle, reg, count, 1, ext, 1);
}

int i2c_read_device(int pi, unsigned handle, char *buf, unsigned count)
{
   int bytes;

   bytes = pigpio_command(pi, PI_CMD_I2CRD, handle, count, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, count, bytes);
   }

   _pmu(pi);

   return bytes;
}

int i2c_write_device(int pi, unsigned handle, char *buf, unsigned count)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=0
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = buf;

   return pigpio_command_ext
      (pi, PI_CMD_I2CWD, handle, 0, count, 1, ext, 1);
}

int i2c_zip(
   int pi,
   unsigned handle,
   char    *inBuf,
   unsigned inLen,
   char    *outBuf,
   unsigned outLen)
{
   int bytes;
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=0
   p3=inLen
   ## extension ##
   char inBuf[inLen]
   */

   ext[0].size = inLen;
   ext[0].ptr = inBuf;

   bytes = pigpio_command_ext
      (pi, PI_CMD_I2CZ, handle, 0, inLen, 1, ext, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, outBuf, outLen, bytes);
   }

   _pmu(pi);

   return bytes;
}

int bb_i2c_open(int pi, unsigned SDA, unsigned SCL, unsigned baud)
{
   gpioExtent_t ext[1];

   /*
   p1=SDA
   p2=SCL
   p3=4
   ## extension ##
   uint32_t baud
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &baud;

   return pigpio_command_ext
      (pi, PI_CMD_BI2CO, SDA, SCL, 4, 1, ext, 1);
}

int bb_i2c_close(int pi, unsigned SDA)
   {return pigpio_command(pi, PI_CMD_BI2CC, SDA, 0, 1);}

int bb_i2c_zip(
   int      pi,
   unsigned SDA,
   char    *inBuf,
   unsigned inLen,
   char    *outBuf,
   unsigned outLen)
{
   int bytes;
   gpioExtent_t ext[1];

   /*
   p1=SDA
   p2=0
   p3=inLen
   ## extension ##
   char inBuf[inLen]
   */

   ext[0].size = inLen;
   ext[0].ptr = inBuf;

   bytes = pigpio_command_ext
      (pi, PI_CMD_BI2CZ, SDA, 0, inLen, 1, ext, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, outBuf, outLen, bytes);
   }

   _pmu(pi);

   return bytes;
}

int spi_open(int pi, unsigned channel, unsigned speed, uint32_t flags)
{
   gpioExtent_t ext[1];

   /*
   p1=channel
   p2=speed
   p3=4
   ## extension ##
   uint32_t flags
   */

   ext[0].size = sizeof(uint32_t);
   ext[0].ptr = &flags;

   return pigpio_command_ext
      (pi, PI_CMD_SPIO, channel, speed, 4, 1, ext, 1);
}

int spi_close(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_SPIC, handle, 0, 1);}

int spi_read(int pi, unsigned handle, char *buf, unsigned count)
{
   int bytes;

   bytes = pigpio_command
      (pi, PI_CMD_SPIR, handle, count, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, count, bytes);
   }

   _pmu(pi);

   return bytes;
}

int spi_write(int pi, unsigned handle, char *buf, unsigned count)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=0
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = buf;

   return pigpio_command_ext
      (pi, PI_CMD_SPIW, handle, 0, count, 1, ext, 1);
}

int spi_xfer(int pi, unsigned handle, char *txBuf, char *rxBuf, unsigned count)
{
   int bytes;
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=0
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = txBuf;

   bytes = pigpio_command_ext
      (pi, PI_CMD_SPIX, handle, 0, count, 1, ext, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, rxBuf, count, bytes);
   }

   _pmu(pi);

   return bytes;
}

int serial_open(int pi, char *dev, unsigned baud, unsigned flags)
{
   int len;
   gpioExtent_t ext[1];

   len = strlen(dev);

   /*
   p1=baud
   p2=flags
   p3=len
   ## extension ##
   char dev[len]
   */

   ext[0].size = len;
   ext[0].ptr = dev;

   return pigpio_command_ext
      (pi, PI_CMD_SERO, baud, flags, len, 1, ext, 1);
}

int serial_close(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_SERC, handle, 0, 1);}

int serial_write_byte(int pi, unsigned handle, unsigned val)
   {return pigpio_command(pi, PI_CMD_SERWB, handle, val, 1);}

int serial_read_byte(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_SERRB, handle, 0, 1);}

int serial_write(int pi, unsigned handle, char *buf, unsigned count)
{
   gpioExtent_t ext[1];

   /*
   p1=handle
   p2=0
   p3=count
   ## extension ##
   char buf[count]
   */

   ext[0].size = count;
   ext[0].ptr = buf;

   return pigpio_command_ext
      (pi, PI_CMD_SERW, handle, 0, count, 1, ext, 1);
}

int serial_read(int pi, unsigned handle, char *buf, unsigned count)
{
   int bytes;

   bytes = pigpio_command
      (pi, PI_CMD_SERR, handle, count, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, buf, count, bytes);
   }

   _pmu(pi);

   return bytes;
}

int serial_data_available(int pi, unsigned handle)
   {return pigpio_command(pi, PI_CMD_SERDA, handle, 0, 1);}

int custom_1(int pi, unsigned arg1, unsigned arg2, char *argx, unsigned count)
{
   gpioExtent_t ext[1];

   /*
   p1=arg1
   p2=arg2
   p3=count
   ## extension ##
   char argx[count]
   */

   ext[0].size = count;
   ext[0].ptr = argx;

   return pigpio_command_ext(
      pi, PI_CMD_CF1, arg1, arg2, count, 1, ext, 1);
}


int custom_2(int pi, unsigned arg1, char *argx, unsigned count,
             char *retBuf, uint32_t retMax)
{
   int bytes;
   gpioExtent_t ext[1];

   /*
   p1=arg1
   p2=retMax
   p3=count
   ## extension ##
   char argx[count]
   */

   ext[0].size = count;
   ext[0].ptr = argx;

   bytes = pigpio_command_ext
      (pi, PI_CMD_CF2, arg1, retMax, count, 1, ext, 0);

   if (bytes > 0)
   {
      bytes = recvMax(pi, retBuf, retMax, bytes);
   }

   _pmu(pi);

   return bytes;
}

int callback(int pi, unsigned user_gpio, unsigned edge, CBFunc_t f)
   {return intCallback(pi, user_gpio, edge, f, 0, 0);}

int callback_ex(
   int pi, unsigned user_gpio, unsigned edge, CBFuncEx_t f, void *user)
   {return intCallback(pi, user_gpio, edge, f, user, 1);}

int callback_cancel(unsigned id)
{
   callback_t *p;
   int pi;

   p = gCallBackFirst;

   while (p)
   {
      if (p->id == id)
      {
         pi = p->pi;

         if (p->prev) {p->prev->next = p->next;}
         else         {gCallBackFirst = p->next;}

         if (p->next) {p->next->prev = p->prev;}
         else         {gCallBackLast = p->prev;}

         free(p);

         findNotifyBits(pi);

         return 0;
      }
      p = p->next;
   }
   return pigif_callback_not_found;
}

int wait_for_edge(int pi, unsigned user_gpio, unsigned edge, double timeout)
{
   int triggered = 0;
   int id;
   double due;

   if ((pi < 0) || (pi >= MAX_PI) || !gPiInUse[pi])
      return pigif_unconnected_pi;

   if (timeout <= 0.0) return 0;

   due = time_time() + timeout;

   id = callback_ex(pi, user_gpio, edge, _wfe, &triggered);

   while (!triggered && (time_time() < due)) time_sleep(0.05);

   callback_cancel(id);

   return triggered;
}

