I'm working on a personal home automation project.
On the server side, I have an Arduino Pro Mini with:
- a 433 MHz TX module on pin 2
- a 433 MHz RX module on pin 3
- a DHT22 probe on pin 4 (with 10k pull-up)
- a DHT22 probe on pin 5 (with 10k pull-up)
I have two absolutely identical of these modules; one will be the radio relay (and DHT "server") and the other a secondary DHT "server".
When it is linked to my laptop (Debian Wheezy) through an FTDI cable, it can read both local probes and switch my wall plugs on/off thanks to a C program I wrote. I'd like to use it from a Raspberry Pi. But on the Raspberry Pi (with the same FTDI cable on USB), it executes the first command I send and then hangs my terminal, forcing me to use CTRL+C.
Here is the sketch on the Arduino side (header) :
/**
* probe.h
*
* @author David Mézière <...>
*/
/**
* DHT probe result
*
*/
struct Probe {
float temperature;
float humidity;
};
Main file :
/**
* probe.ino
*
* @author David Mézière <...>
*/
#include "probe.h"
/**
* Uses DHT sensor library, from Adafruit.
* @see https://github.com/adafruit/DHT-sensor-library
*/
#include <DHT.h>
/**
* Uses RC Switch library, from sui77.
* @see https://github.com/sui77/rc-switch
*/
#include <RCSwitch.h>
// Pinout definitions
#define TX 2 // 433 MHz transmitter pin number
#define RX 3 // 433 MHz receiver pin number
#define PROBE1 4 // First DHT22 probe pin number
#define PROBE2 5 // Second DHT22 probe pin number
#define LED 13 // On-board status LED pin number
RCSwitch radio = RCSwitch();
// DHT probes definition
DHT dht1(PROBE1, DHT22);
DHT dht2(PROBE2, DHT22);
// Incomming command buffer
byte cmd[9];
/**
* Setup
*
* @return void
*/
void setup()
{
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW);
Serial.begin(9600);
Serial.setTimeout(1000); // doesn't fix the problem
// Attach receiver to interrupt 1, meaning pin 3
radio.enableReceive(1);
radio.enableTransmit(TX);
dht1.begin();
dht2.begin();
// Debug: Internal LED will blink 3 times rapidly to show when a reboot occurs.
for (int i = 0; i < 3; i++) {
digitalWrite(LED, HIGH);
delay(250);
digitalWrite(LED, LOW);
delay(250);
}
}
/**
* Loop
*
* @return void
*/
void loop()
{
if (Serial.available() == 9 && readCommand()) {
// Lights-up internal LED to show when a command has been executed
digitalWrite(LED, HIGH);
delay(1000);
digitalWrite(LED, LOW);
}
}
/**
* Query probe
*
* Query provided [dht] probe until [retry] times for both temperature and humidity.
*
* @param DHT dht Pointer to DHT object
* @param int retry Number of tries
* @return Probe Probe result (float temperature in °C, float humidity in %)
*/
Probe queryProbe(DHT* dht, int retry)
{
Probe probe;
// Query DHT22 probe for temperature, a maximum of [retry] times.
for (int t = 0; t < retry; t++) {
probe.temperature = dht->readTemperature(false);
if (!isnan(probe.temperature)) {
break;
}
delay(50);
}
// Query DHT22 probe for humidity, a maximum of [retry] times.
for (int h = 0; h < retry; h++) {
probe.humidity = dht->readHumidity();
if (!isnan(probe.humidity)) {
break;
}
delay(50);
}
return probe;
}
/**
* Read command
*
* If serial buffer contains 2 bytes, move them to a local buffer and return true. else return false.
*
* @return boolean
*/
boolean readCommand()
{
// Reads the current buffer
Serial.readBytes(cmd, 9);
// Calculates the check sum of payload
int sum = cmd[2] ^ cmd[3] ^ cmd[4] ^ cmd[5] ^ cmd[6] ^ cmd[7];
// Checking header and checksum a header of 0xBA 0xB1 means DHT query
if (cmd[0] == 0xBA && cmd[1] == 0xB1 && cmd[8] == sum) {
unsigned int module = cmd[2];
unsigned int probe = (cmd[4] << 24) + (cmd[5] << 16) + (cmd[6] << 8) + cmd[7];
Probe result;
switch (module) {
case 1:
// Selects the right probe
if (probe == 1) {
result = queryProbe(&dht1, 3);
} else if (probe == 2) {
result = queryProbe(&dht2, 3);
}
// Send status repport to client
Serial.print("1;");
Serial.print(module);
Serial.print(";");
Serial.print(probe);
Serial.print(";");
Serial.print(result.temperature);
Serial.print(";");
Serial.println(result.humidity);
Serial.flush(); // Doesn't fix the problem
break;
}
return true;
// A header of 0xBA 0xB2 means rf wall plugs query
} else if (cmd[0] == 0xBA && cmd[1] == 0xB2 && cmd[8] == sum) {
unsigned int proto = cmd[2];
unsigned int length = cmd[3];
unsigned int value = (cmd[4] << 24) + (cmd[5] << 16) + (cmd[6] << 8) + cmd[7];
radio.send(value, length);
// Send status repport to client
Serial.print("2;");
Serial.print(proto);
Serial.print(";");
Serial.print(length);
Serial.print(";");
Serial.print(value);
Serial.print(";");
Serial.println("OK");
Serial.flush(); // Doesn't fix the problem
return true;
} else {
Serial.println("KO");
Serial.flush(); // Doesn't fix the problem
return false;
}
}
And on the client side :
/**
* probe.c
*
* @author David Mézière <...>
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <getopt.h>
const char* device;
static int module = 0; // uint_8 ?
static int probe = 0; // uint_8 ?
const char* proto;
static int length = 0; // uint_8 ?
static int value = 0; // uint_32 ?
static int verbose = 0; // uint_8 ?
void help()
{
printf("usage:\n");
printf("\n");
printf("probe [options] [arguments]\n");
printf("\n");
printf("options:\n");
printf(" -h|--help: Displays this help and exit\n");
printf(" -v|--verbose: Be more verbose\n");
printf("\n");
printf("arguments:\n");
printf(" -d|--device: string Serial device to use (ex: /dev/ttyUSB0)\n");
printf(" -m|--module: integer DHT22 module to query\n");
printf(" -p|--probe: integer DHT22 probe to query\n");
printf(" -r|--proto: string Radio / IR protocol\n");
printf(" -l|--length: integer Radio / IR value length in bits\n");
printf(" -a|--value: integer Radio / IR value\n");
printf("\n");
printf("examples:\n");
printf(" probe --device /dev/ttyUSB0 --module 1 --probe 1 : Will query first DHT22 probe of first module\n");
printf(" probe --proto radio1 --length 12 --value 5393 : Will send value 5393 on 12 bits over the air using protocol 1\n");
printf(" probe --proto ir11 --length 64 --value 3772793023 : Will send value 3772793023 on 64 bits by infra red using protocol 11\n");
}
void parseArgs(int argc, char **argv)
{
int c;
while (1) {
static struct option long_options[] = {
{"device", required_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{"module", required_argument, 0, 'm'},
{"probe", required_argument, 0, 'p'},
{"proto", required_argument, 0, 'r'},
{"length", required_argument, 0, 'l'},
{"value", required_argument, 0, 'a'},
{"verbose", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
/* getopt_long stores the option index here. */
int option_index = 0;
c = getopt_long(argc, argv, "d:hm:p:v", long_options, &option_index);
/* Detect the end of the options. */
if (c == -1) {
break;
}
switch (c) {
case 0:
/* If this option set a flag, do nothing else now. */
if (long_options[option_index].flag != 0) {
break;
}
printf("option %s", long_options[option_index].name);
if (optarg) {
printf (" with arg %s", optarg);
}
printf("\n");
break;
case 'd':
device = optarg;
break;
case 'h':
help();
exit(0);
break;
case 'm':
module = atoi(optarg);
break;
case 'p':
probe = atoi(optarg);
break;
case 'r':
proto = optarg;
break;
case 'l':
length = atoi(optarg);
break;
case 'a':
value = atoi(optarg);
break;
case 'v':
verbose = 1;
break;
case '?':
/* getopt_long already printed an error message. */
break;
default:
abort();
}
}
/* Print any remaining command line arguments (not options). */
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc) {
printf("%s ", argv[optind++]);
}
putchar('\n');
}
if (&device[0] == '\0') {
fprintf(stderr, "--device is mandatory\n");
exit(1);
} else if (verbose) {
printf("Device: %s\n", device);
}
if (verbose) {
printf("Querying probe %i of module %i.\n", probe, module);
}
}
void initSerial(int fd)
{
struct termios toptions;
/* get current serial port settings */
tcgetattr(fd, &toptions);
/* set 9600 baud both ways */
cfsetispeed(&toptions, B9600);
cfsetospeed(&toptions, B9600);
/* 8 bits, no parity, no stop bits */
toptions.c_cflag &= ~PARENB;
toptions.c_cflag &= ~CSTOPB;
toptions.c_cflag &= ~CSIZE;
toptions.c_cflag |= CS8;
/* Canonical mode */
toptions.c_lflag |= ICANON;
/* commit the serial port settings */
tcsetattr(fd, TCSANOW, &toptions);
}
int main(int argc, char **argv)
{
// Parses command line arguments
parseArgs(argc, argv);
int fd, n, i;
char buf[64] = "temp text";
/* open serial port */
fd = open(device, O_RDWR | O_NOCTTY);
if (verbose) {
printf("Device %s opened as %i\n", device, fd);
}
/*
* Note: Most Arduinos models will reboot upon connection, and they need
* some time for it. I use a pro/mini that doesn't, so i commented it out.
*/
// usleep(3500000);
// Sets the serial port settings (9600 bps, 8 bits, no parity, no stop bits)
initSerial(fd);
/**
* 72 bits
* | Header | Param 1 | Param 2 | Param 3 | sum |
* | 16 b | 8 b | 8 b | 32 b | 8 b |
* Cas 1 : Requête DHT | 0xba 0xb1 | module | 0x00 | sonde | sum |
* Cas 2 : Requête radio | 0xba 0xb2 | proto | length | value | sum |
* Cas 3 : Requête IR | 0xba 0xb3 | proto | length | value | sum |
*/
unsigned char oBuf[9];
// printf("%s\n", proto);
// printf("%i\n", length);
if (module > 0 && probe > 0) {
if (verbose) {
printf("DHT mode\n");
}
oBuf[0] = 0xBA;
oBuf[1] = 0xB1; // DHT query
oBuf[2] = module;
oBuf[3] = 0x00;
oBuf[4] = (probe >> 24) & 0xFF;
oBuf[5] = (probe >> 16) & 0xFF;
oBuf[6] = (probe >> 8) & 0xFF;
oBuf[7] = probe & 0xFF;
oBuf[8] = oBuf[2];
oBuf[9] = '\n';
// Calculates the XOR sum
for (i = 3; i < 8; i++) {
oBuf[8] ^= oBuf[i];
}
// sprintf(oBuff, "%c%c%c%c%c%c", 0xba, 0xb1, module, 0x00, probe, sum);
} else if (strcmp((const char*)proto, "radio1") == 0 && length > 0) {
if (verbose) {
printf("Radio mode\n");
}
oBuf[0] = 0xBA;
oBuf[1] = 0xB2; // Radio query
oBuf[2] = 0x01; // Protocol 1
oBuf[3] = length;
oBuf[4] = (value >> 24) & 0xFF;
oBuf[5] = (value >> 16) & 0xFF;
oBuf[6] = (value >> 8) & 0xFF;
oBuf[7] = value & 0xFF;
oBuf[8] = oBuf[2];
oBuf[9] = '\n';
// Calculates the XOR sum
for (i = 3; i < 8; i++) {
oBuf[8] ^= oBuf[i];
}
} else {
if (verbose) {
printf("Unknown mode\n");
}
}
/* Send the buffer */
write(fd, oBuf, 9);
/* Receive string from Arduino */
n = read(fd, buf, 64);
/* insert terminating zero in the string */
buf[n] = 0;
if (verbose) {
printf("%i bytes read, buffer contains: %s\n", n, buf);
} else {
printf("%s", buf);
}
return 0;
}
I compile it using just gcc probe.c -o probe
.
On Debian, I can use the system as much as I want, it works:
dmeziere@portable2-wlan:~/dev/probe$ gcc probe.c -o probe
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 1
1;1;1;23.60;43.10
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 2
1;1;2;23.60;38.50
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --proto radio1 --length 24 --value 5396
2;1;24;5396;OK
dmeziere@portable2-wlan:~/dev/probe$ ./probe --device /dev/ttyUSB0 --proto radio1 --length 24 --value 5393
2;1;24;5393;OK
On Raspbian, the first call works, but the second hangs my terminal and I have to do CTRL+C:
dmeziere@raspberrypi:~/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 1
1;1;1;23.90;39.00
dmeziere@raspberrypi:~/probe$ ./probe --device /dev/ttyUSB0 --module 1 --probe 2
^C
I found it !
It seems like on Raspbian, the communications were terminated by a NULL character. But not on Debian. And this NULL character was polluting my calculations of incomming buffer length. So i ended up by eventually suppress it on the Arduino side :