After far too many revisions, the GPIO interface for the Raspberry Pi alarm system is nearly done. It has:
• 8 Digital Inputs
• 4 Relays
• 1 RS-232 serial port
• 2 SPI bus connectors
• 1 I2C bus connector (eventually)
I still need to to finish wiring the SPI connectors, but the serial port, the digital inputs, and the relays are all working. I will probably go ahead and add connectors for the I2C bus pins for future use. I already have an eight port A/D converter for the SPI bus and more GPIO would also be easy to add via SPI.
I added a jumper block near the GPIO connector that allows me to connect (or not) 5V on the interface to 5V on the RasPi. This would allow me to power the board from the RasPi or to backfeed power to the RasPi from the board. I plan on making a jumper with a polyfuse inline. Using that as the jumper will prevent the RasPi from drawing too much current.
Monday, October 15, 2012
Monday, October 8, 2012
Source Code - Installment One
Edit 15-Oct-2012: Posted corrected code - missed a few typos. GpioPoller.c is now the multiplexed version. Edit to make HTML behave nicely.
I have been looking at this source code issue from the wrong perspective. I knew that I would be posting my source code here eventually, but I didn't think that it would be useful to that many people. That was when tunnel vision had me thinking of just this alarm system project.
The example code that I present here is really much more widely applicable. This is my main function, which implements a daemon process in C. Also included are my data structures and my method for using worker threads. Copious comments have been added to help clarify things.
RPiHouse.h data structures, function prototypes, and global variables
main.c The entry point for the program
GpioPoller.c The GPIO polling thread
I have been looking at this source code issue from the wrong perspective. I knew that I would be posting my source code here eventually, but I didn't think that it would be useful to that many people. That was when tunnel vision had me thinking of just this alarm system project.
The example code that I present here is really much more widely applicable. This is my main function, which implements a daemon process in C. Also included are my data structures and my method for using worker threads. Copious comments have been added to help clarify things.
RPiHouse.h data structures, function prototypes, and global variables
/*---------------------------------------------------------------------------
RPiHouse.h - include file for the Raspberry Pi re-write of controld
08-Aug-2012 Ted Hale add enums and new dev struct
08-Oct-2012 Cleanup and comments for release of source
---------------------------------------------------------------------------*/
#define PIDFILE "/var/run/RPiHouse.pid"
#define CONFIGFILE "/pihome/RPiHouse.conf"
#define DEVICEFILE "/pihome/devices.conf"
#define MAXDEVICES 100
#define MYPORT 17100
#define MAXCONNECTIONS 10
#define BYTE unsigned char
//#include "mysql.h"
//#include "mysqld_error.h"
// causes Global variables to be defined in the main
// and referenced as extern in all the other source files
#ifndef EXTERN
#define EXTERN extern
#endif
// device types
typedef enum
{
X10,
Gout,
Gin
} DevType;
// device categories
typedef enum
{
Light,
OutdoorLight,
MotionSensor,
DoorSensor,
Other
} DevCategory;
// the device structure
typedef struct
{
char *name;
DevType type;
BYTE addr;
BYTE house;
DevCategory category;
char *oncmd;
char *offcmd;
int stat;
time_t tOn;
time_t tOff;
} Device;
// "at" commands structure. this is used in a linked list
typedef struct
{
char *cmd; // command to perform
time_t time; // when to perform it
int period; // -1: one time, else seconds to add for next time
void *next; // next in queue
} At_qEntry;
// prototype definitions for the worker threads
void *ListenerThread(void *param);
void *GpioPoller(void *param);
void *X10Thread(void *param);
void *LogicThread(void *param);
// some other prototype definitions
int DoCommands(char *Cmd);
int LogToClients(char *format, ... );
int AtCmd(char *cmd);
void X10TurnOn(char *dev);
void X10TurnOff(char *dev);
void DoTurnOnOff(char *name, int onoff);
char *CatName(int n);
char *TypeName(int n);
// GLOBAL variables. A lock needs to be used to prevent any
// simultaneous access from multiple threads
EXTERN int kicked; // flag for shutdown or restart
EXTERN int nDevices; // number of devices defined
EXTERN Device dev[MAXDEVICES]; // the array of devices
///EXTERN MYSQL *conn; // the DB connection
EXTERN int logsock[MAXCONNECTIONS]; // log listeners
EXTERN time_t Sunrise; // time of sunrise for today
EXTERN time_t Sunset; // time of sunset for today
main.c The entry point for the program
/*---------------------------------------------------------------------------
main.c
By Ted B. Hale
part of the home alarm and automation system previously known as "control"
renamed to RPiHouse for this rebuild on the Raspberry Pi
This file implements the main for a daemon process
01-Nov-2009 Starting over from scratch (mostly) on Linux
23-Jul-2010 on VersaLogic Jaguar embedded system now
disabled weather thread
03-Aug-2012 re-write for Raspberry Pi
08-Oct-2012 Cleanup and comments for release of source
---------------------------------------------------------------------------*/
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/timeb.h>
#include <pthread.h>
// database not used yet
//#define dbhost "localhost"
//#define dbuser "control"
//#define dbpass "secret"
//#define dbdatabase "control"
// this defines the pre-processor variable EXTERN to be nothing
// it results in the variables in RPiHouse.h being defined only here
#define EXTERN
#include "RPiHouse.h"
//************************************************************************
// reads the device file defined in RpiHouse.h andsets up the dev table
int ReadDevices()
{
FILE *f;
char line[200];
char *p;
int i, n;
// open the device config file
f = fopen(DEVICEFILE,"r");
if (!f)
{
Log("Failed to open device file [%s]\n",CONFIGFILE);
return 0;
}
// read lines from the file. It is structured like a windows ini file
// where the device names are the section names ( enclosed in [] )
nDevices = 0;
n = -1;
while (read_line(f,line)>=0)
{
//Log("READCONFIG: %s",line);
// these are all lines to ignore - comments and lines that are blank
if ((line[0]==';')||(line[0]=='#')||(line[0]==' ')||(line[0]==0))
continue;
// is this a device name
if (line[0]=='[')
{
n++; nDevices = n+1;
// is the table full
if (nDevices>=MAXDEVICES)
{
Log(" ***** Out of devices *****");
break;
}
// add a new device to the table
dev[n].name = strdup(line+1);
p = strchr(dev[n].name,']');
if (p) *p = 0;
dev[n].stat = 0;
dev[n].tOn = 0;
dev[n].tOff = 0;
continue;
}
// ignore everything until a device is defined
if (nDevices==0) continue;
// parse out "variable=value"
// get pointer to =
p = strchr(line,'=');
// if no =, then skip
if (!p)
continue;
// this will put a null terminator after the variable name
*p=0;
// bump p by 1 to have it point at the value
p++;
// the line may have a newline or other character on the end
// this will fix that
if (p[strlen(p)-1]<' ')
p[strlen(p)-1] = 0;
// set specified variable
// device type
if (!strcmp(line,"type"))
{
if (!strcasecmp (p,"X10"))
{
dev[n].type = X10;
}
else if (!strcasecmp (p,"Gout"))
{
dev[n].type = Gout;
}
else if (!strcasecmp (p,"Gin"))
{
dev[n].type = Gin;
}
}
// device address
if (!strcmp(line,"addr"))
{
dev[n].addr = atoi(p);
}
// house code part of address for X10 devices
if (!strcmp(line,"house"))
{
dev[n].house = *p-'A';
}
// device category
if (!strcmp(line,"category"))
{
if (!strcasecmp (p,"Light"))
{
dev[n].category = Light;
}
else if (!strcasecmp (p,"MotionSensor"))
{
dev[n].category = MotionSensor;
}
else if (!strcasecmp (p,"DoorSensor"))
{
dev[n].category = DoorSensor;
}
else
{
dev[n].category = Other;
}
}
// execute this command when the device turns on
// mostly useful for GPIO inputs but can be used for X10 too
if (!strcmp(line,"on"))
{
dev[n].oncmd = strdup(p);
}
// same for when it turns off
if (!strcmp(line,"off"))
{
dev[n].offcmd = strdup(p);
}
}
///Log("Done reading devices");
// close the config file
fclose(f);
// output a table of the devices to the log file
for (i=0; i<nDevices; i++)
{
Log(" %-20s %-15s %-5s address: %2d %2d",
dev[i].name, CatName(dev[i].category),
TypeName(dev[i].type), dev[i].addr, dev[i].house);
}
return 0;
}
//************************************************************************
// handles signals to restart or shutdown
void sig_handler(int signo)
{
switch (signo) {
case SIGPWR:
break;
case SIGHUP:
// do a restart
Log("SIG restart\n");
LogToClients("SIG restart");
kicked = 1;
break;
case SIGINT:
case SIGTERM:
// do a clean exit
Log("SIG exit\n");
LogToClients("SIG exit");
kicked = 2;
break;
}
}
//************************************************************************
// and finally, the main program
// a cmd line parameter of "f" will cause it to run in the foreground
// instead of as a daemon
int main(int argc, char *argv[])
{
pid_t pid;
FILE *f;
pthread_t tid1,tid2,tid3,tid4,tid5; // thread IDs
struct tm *today;
// check cmd line param
if ((argc==1) || strncmp(argv[1],"f",1))
{
//printf("going to daemon mode\n");
// Spawn off a child, then kill the parent.
// child will then have no controlling terminals,
// and will become adopted by the init proccess.
if ((pid = fork()) < 0) {
perror("Error forking process ");
exit (-1);
}
else if (pid != 0) {
exit (0); // parent process goes bye bye
}
// The child process continues from here
setsid(); // Become session leader;
}
// trap some signals
signal(SIGTERM, sig_handler);
signal(SIGINT, sig_handler);
signal(SIGPWR, sig_handler);
signal(SIGHUP, sig_handler);
// save the pid in a file
pid = getpid();
f = fopen(PIDFILE,"w");
if (f) {
fprintf(f,"%d",pid);
fclose(f);
}
// open the debug log
LogOpen("/pihome/logs/RPiHouse");
// database not added back yet
// init MySQL interface
/* conn = mysql_init(NULL);
if (conn == NULL) {
Log("mysql_init Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
} else {
if (mysql_real_connect(conn, dbhost, dbuser, dbpass, dbdatabase, 0, NULL, 0) == NULL) {
Log("Error %u: %s\n", mysql_errno(conn), mysql_error(conn));
mysql_close(conn);
conn = NULL;
}
}*/
// start the main loop
do
{
LogToClients("STARTING");
// read config info
ReadDevices();
// start the various threads
tid1 = tid2 = tid3 = tid4 = tid5 = 0;
pthread_create(&tid1, NULL, GpioPoller, NULL);
pthread_create(&tid2, NULL, X10Thread, NULL);
pthread_create(&tid3, NULL, LogicThread, NULL);
pthread_create(&tid4, NULL, ListenerThread, NULL);
///pthread_create(&tid5, NULL, MiscThread, NULL);
// wait for signal to restart or exit
do
{
sleep(1);
} while (!kicked);
// wait for running threads to stop
if (tid1!=0) pthread_join(tid1, NULL);
if (tid2!=0) pthread_join(tid2, NULL);
if (tid3!=0) pthread_join(tid3, NULL);
if (tid4!=0) pthread_join(tid4, NULL);
if (tid5!=0) pthread_join(tid5, NULL);
// exit?
if (kicked==2) break;
// else restart, set flag back to 0
kicked = 0;
} while (1); // forever
// delete the PID file
unlink(PIDFILE);
return 0;
}
GpioPoller.c The GPIO polling thread
/*---------------------------------------------------------------------------
GpioPoller.c Poll the GPIO devices via the wiringPi interface
Ted Hale
08-Aug-2012 initial version for Raspberry Pi re-write of controld
08-Oct-2012 Cleanup and comments for release of source
14-Oct-2012 modify for muxed input
---------------------------------------------------------------------------*/
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/timeb.h>
#include <pthread.h>
#include <wiringPi.h>
#include "RPiHouse.h"
// Thread entry point, param is not used
void *GpioPoller(void *param)
{
int pin, i, x, a0, a1, a2;
// initialize the WireingPi interface
Log("GpioPoller: init wiringPi");
if (wiringPiSetup () == -1)
{
Log("Error on wiringPiSetup. GpioPoller thread quitting.");
return;
}
Log("GpioPoller: init devices");
// initialize input circuit
// muxed input - 0 is input 1-3 are address bits
pinMode (0, INPUT);
pullUpDnControl(0,PUD_UP);
for (i=1; i<4; i++)
{
pinMode (i, OUTPUT);
digitalWrite(i, 0);
}
// relay outputs are 4-7
for (i=4; i<8; i++)
{
pinMode (i, OUTPUT);
digitalWrite(i, 0);
}
// start polling loop
do
{
for (i = 0 ; i < nDevices ; i++)
{
switch (dev[i].type)
{
case Gin:
// set mux address
a0 = ((dev[i].addr & 1)==1)?1:0;
a1 = ((dev[i].addr & 2)==2)?1:0;
a2 = ((dev[i].addr & 4)==4)?1:0;
digitalWrite(1, a0);
digitalWrite(2, a1);
digitalWrite(3, a2);
// mux needs a tiny amount of time for the value to settle
Sleep(0);
// input is pulled high, so 1 is off and 0 (shorted to ground) is on
x = digitalRead(0);
// if on
if (x==0) {
// and it wasn't already on
if (!dev[i].stat) {
Log("GpioPoller> %s ON\n",dev[i].name);
LogToClients("%s ON",dev[i].name);
/////DoCommands(dev[i].oncmd);
}
time(&dev[i].tOn);
dev[i].stat=1;
} else {
// if Off
// and it wasn't already off
if (dev[i].stat) {
Log("micropoller> %s OFF\n",dev[i].name);
LogToClients("%s OFF",dev[i].name);
/////DoCommands(dev[i].offcmd);
}
time(&dev[i].tOff);
dev[i].stat=0;
}
// Save to DB
// ??? this should be only when the value changes !!!
/*if (conn!=NULL) {
sprintf(sql,"insert into data (var,val) VALUES ('%s',%d)",dev[i].name,dev[i].stat);
if (mysql_query(conn, sql)) {
Log("mysql_query Error sql: %s\n errno = %u: %s",
sql, mysql_errno(conn), mysql_error(conn));
}
}*/
break;
case Gout:
// do nothing
break;
default:
// other modes not yet supported
break;
}
}
// let other thread run, sleep 10ms
Sleep(10);
} while (kicked==0); // exit loop if flag set
}
Folder Paper Case
I saw a post on the RasPi forum today about a new folder paper case and I thought I would try it out since my RasPi number 4 needs a home.
http://www.iammer.com/raspi/case.html
I printed it on heavy card stock and on regular paper. The regular paper was used for practice. Good idea since I found that I folded it upside down. Fold it with the printed side down. I used scissors, an exacto knife and an old piece of wood as a cutting board. Here is how it went.
I like the result. Especially for a case that is basically free (assuming you can get a sheet of heavy card stock.) The plain white looks really boring, but it should be pretty easy to add whatever design you like.
Flip the case over and there is lots of space to express your inner Pi. OK, so I'm not Picasso.
http://www.iammer.com/raspi/case.html
I printed it on heavy card stock and on regular paper. The regular paper was used for practice. Good idea since I found that I folded it upside down. Fold it with the printed side down. I used scissors, an exacto knife and an old piece of wood as a cutting board. Here is how it went.
All it takes - printed card stock, scissors, and a knife. |
After being cut out. |
Make good creases on all the folds. |
The result - Very functional and surprisingly study little box. |
The connectors and SD card hold the RasPi tightly in place. |
Flip the case over and there is lots of space to express your inner Pi. OK, so I'm not Picasso.
Tuesday, October 2, 2012
Revision to Relay Circuit
After some thought and further research I made the following
changes to the GPIO relay control circuit.
The current limiting resistor is now 10K and I will explain why in a
moment. Also, a 100KΩ resistor has been
added. This is recommended to ensure
that the transistor turns off if the input is left open. In my design, the input is always either high
or low and is never open, so I am leaving this out of my interface.
The current limiting resistor must be sized to allow enough
current to saturate the base of the transistor to guarantee that it
switches. Current too far above this
level could damage the transistor. Every
transistor has an inherent property known as the common emitter current gain,
commonly referred to as HFE. The proper
size for the resistor may be calculated using the following formula.
R = Supply Voltage /
( max A / HFE * 1.3 )
The supply voltage (from the GPIO pin) is 3.3V.
The HFE for a 2N2222 transistor is assumed to be at least 100. The current draw on the relays that I use
ranges between 30 and 50mA. Plugging in
the numbers for 30mA gives a result of R = 8461. If 50mA is used, then R = 5076. However, the HFE is very likely higher. If we assume that it is 150, then the resistor value at 30mA becomes 12692. This shows that a 10KΩ resistor is probably more
appropriate for this configuration.
The bottom line is that any resistor between 1K and 10K should work OK. I just felt that this issue should be addressed before any arguments start.
Subscribe to:
Posts (Atom)