Weather Station and the Raspberry Pi

USB to RS-485 Adapter
USB to RS-485 Adapter

One of the weaknesses with my weather stations was that they did not have the ability to accept incoming connections. This made remote debug a bit difficult, if not impossible. Basically, you program the thing, and hope there are no glitches. Surprisingly, this worked for many years with the occasional reset, but I finally decided to spend some time making some improvements to this aspect of the weather station system.

Originally, my weather stations consisted of an outdoor unit, which acted as a hub for all the outdoor sensors. The hub communicated with an RS-485 to Ethernet bridge (Arduino+Ethernet shield) inside the house near the router. This allowed long runs, and the ability to easily chain multiple hubs together on the RS-485 bus. I decided to keep the RS-485 bus model, and built a USB to RS-485 bridge to replace the Arduino and Ethernet shield that I used previously. The new USB adapter uses an FTDI chip and is compatible with most Mac, Linux, and Windows computers out-of-the-box. I was really happy to avoid the use of drivers. This adapter also features transient protection and other features that most cheap USB to RS-485 adapters do not have. This was important considering the long cable run out to the weather station. And, because the weather station hub runs at 3.3V and uses little power, the entire system can be powered from the USB port which provides 5V down the line.

I took this new USB adapter and hooked it up to a Raspberry Pi. Using a Python script I whipped up in a few minutes I was able to completely replace the functionality of the older Arduino-based system. While I haven’t added any new functionality yet, the Raspberry Pi allows for a lot more possibilities with its huge memory space, and the ability to SSH and VNC into it remotely. This solves the old problem of remote troubleshooting, which was basically impossible before. Local logs are also generated using the Python script to record errors and weather data in the event of an internet outage or other failure. I’m very happy with this improvement.

38kHz TV Remote Hacking

Arduino nano with a VS1838 IR reciever, and a IR LED.
Arduino nano with a VS1838 IR reciever, and a IR LED.
Arduino Nano

Most remotes used by TVs, DVD Players, and other consumer electronics use infrared light to send commands from the remote to the device being controlled. This is done because of the low cost and simplicity that infrared-based remotes offer.

The technology behind these remotes is simple. The transmitter consists of an infrared LED that is modulated at 38kHz. The LED is activated for varying amounts of time to send the information required to control the device.

An Arduino can be used to implement an effective remote control system. The following code was compiled on an Arduino. It performs the following functions:

  • Receive and decode standard remote-control commands
  • Print decoded commands over the serial port
  • Retransmitt the decoded commands using an infrared LED

The basic result is a infrared remote repeater. It will emulate any remote it can successfully detect by sending the received commands back out via the infrared LED.

Parts List:
  • VS1838B 38khz Reciever (From Ebay)
  • 940nm Infrared Led (From Ebay)
  • Arduino Nano Clone (From Ebay)
Arduino Code:
#define IRRX_GND 3
#define IRRX_PWR 4
#define IRRX_OUT 2
#define IRLEDPIN A3

byte bosePwr[4]   = {0xBA, 0xA0, 0x4c, 0xb3};
long time;
byte counter;
bool flag;
byte message[4];

void setup() {
	attachInterrupt(0,interrupt,CHANGE);
	pinMode(IRRX_GND,OUTPUT);
	pinMode(IRRX_PWR,OUTPUT);
	pinMode(IRLEDPIN,OUTPUT);
	digitalWrite(IRRX_GND,LOW);
	digitalWrite(IRRX_PWR,HIGH);
	Serial.begin(9600);
	Serial.println("Ready");
}

void loop() {
	long timer = micros() - time;
	if(flag){
		flag = 0;
		if(timer > 1400 && timer < 2000){ //Logic 1 addBit(1); }else if(timer > 450 && timer < 650){ //Logic 0 addBit(0); }else if(timer > 4000 && timer < 5000){ //START clearBits(); }else if(timer > 2000 && timer < 2500){ //REPEAT sendRpt(); clearBits(); } } if(counter >= 32){ //Recieve 32 or more bits successfully
		printBits();
		delay(50);
		sendCommand(message);
		clearBits();
	}

	if(timer > 10000 && counter > 0 && counter < 32){ //10ms Timeout
		Serial.println("Incomplete code");
		clearBits();
	}
}

void addBit(byte data){
	byte block = counter / 8;
	byte spot = counter % 8;
	byte mask = data << spot;
	message[block] |= mask;
	counter++;
}

void clearBits(){
	counter = 0;
	for(int i = 0; i < 4; i++){
		message[i] = 0;
	}
}

void printBits(){
	for(int i = 0; i < 4; i++){ byte low = message[i] & B00001111; byte high = (message[i] & B11110000) >> 4;
		Serial.print(high,HEX);
		Serial.print(low,HEX);
		Serial.print(" ");
	}
	Serial.println();
}

void interrupt(){
	if(!digitalRead(IRRX_OUT)) flag = 1;
	else time = micros();
}

void sendCommand(byte command[]) {
	sendHeader();
	for(int i = 0; i < 4; i++){
		sendByte(command[i]);
	}
	sendFooter();
}

void sendByte(byte data){
	byte mask = B00000001;
	for(int j = 0; j < 8; j++){
		if(mask & data) send1();
		else send0();
		mask <<= 1; } } void send1(){ send38KHz(560); delayMicroseconds(1690); } void send0(){ send38KHz(560); delayMicroseconds(560); } void sendHeader(){ send38KHz(9000); delayMicroseconds(4500); } void sendFooter(){ send38KHz(560); } void sendRpt(){ send38KHz(9000); delayMicroseconds(2250); send38KHz(2250); } void send38KHz(long microsecs) { while (microsecs > 0) {
		// 38 kHz -> 26 uSec (Duty cycle = 1/3)
		digitalWrite(IRLEDPIN, HIGH); //takes ~ 5uSec
		delayMicroseconds(6);
		digitalWrite(IRLEDPIN, LOW); //takes ~ 5uSec
		delayMicroseconds(10);
		microsecs -= 26;
	}
}

Weather Station Facelift

Google Interactive Charts

The online user interface for my weather station project got a nice face lift recently. I’ve managed to streamline some of the php code and do some formatting using bootstrap which resulted in a much more pleasing GUI. I also am in the process of adding humidity measurements to my weather stations, but I’m not happy with the current platform. Ideally I’d like to have all digital sensors to avoid noise problems that I’m dealing with using long cable runs to outdoor locations. Another option might be using an ADC at the sensor with enough resolution to give a precise temperature reading.

I’ll be exploring this problem more in my free time, but for now, take a look at the updated display page. Also, thanks to the generosity of family in Spokane, I now have two identical weather stations taking measurements, and they both can be accessed online in real-time thanks to the recent changes I made in the php code.

HF beacon using Arduino

I recently got a cheap 40MHz signal generator board off of ebay for a few bucks. This board is based on the Analog Devices AD9850, but the ones you find on eBay are probably knock-offs.

Parts List:
Source Code:

I modified some code I found online to use it to be able to send Morse code from the serial port on my computer using putty. Putty is a nice piece of free serial terminal software. The output power is very low (easily measured in microWatts with a small antenna) but after some impedance matching and a amplifier stage you could easily use this for a nice HF beacon project. Here is the code if you want to try it for yourself:

Arduino Code:
//Has the ability to send morse code from the serial port

#define WPM 20
#define pttOut 13
#define pwmOut 5
#define toneFrequency 400  //Hz
#define W_CLK 8       // Pin 8 - connect to AD9850 module word load clock pin (CLK)
#define FQ_UD 9       // Pin 9 - connect to freq update pin (FQ)
#define DATA 10       // Pin 10 - connect to serial data load pin (DATA)
#define RESET 11      // Pin 11 - connect to reset pin (RST)
#define txfrequency 14015000

byte morseLookup[] = {
	//Letters
	B01000001,B10001000,B10001010,B01100100,B00100000,B10000010,
	B01100110,B10000000,B01000000,B10000111,B01100101,B10000100,
	B01000011,B01000010,B01100111,B10000110,B10001101,B01100010,
	B01100000,B00100001,B01100001,B10000001,B01100011,B10001001,
	B10001011,B10001100,
	//Numbers
	B10111111,B10101111,B10100111,B10100011,B10100001,B10100000,
	B10110000,B10111000,B10111100,B10111110,B10111110,
	//Slash
	B10110010
};


void setup(){
	Serial.begin(9600);
	Serial.print("Loading...");
	pinMode(pttOut,OUTPUT);
	pinMode(pwmOut,OUTPUT);
	pinMode(4,OUTPUT);
	digitalWrite(4,LOW);
	pinMode(3,OUTPUT);
	digitalWrite(3,HIGH);
	setupDDS();
	delay(500);
	Serial.println(" complete");
}

void loop(){
	if(Serial.available()){sendSerialMessage();}
	transmitString("Test");
	delay(5000);
}

// transfers a byte, a bit at a time, LSB first to the 9850 via serial DATA line
void tfr_byte(byte data)
{
	for (int i=0; i<8; i++, data>>=1) {
		digitalWrite(DATA, data & 0x01);
		pulseHigh(W_CLK);   //after each bit sent, CLK is pulsed high
	}
}

void sendFrequency(double frequency) {// frequency calc from datasheet page 8 =  * /2^32
	int32_t freq = frequency * 4294967295/125000000;  // note 125 MHz clock on 9850
	for (int b=0; b<4; b++, freq>>=8) {
		tfr_byte(freq & 0xFF);
	}
	tfr_byte(0x000);   // Final control byte, all 0 for 9850 chip
	pulseHigh(FQ_UD);  // Done!  Should see output
}

void pulseHigh(int pin){
	digitalWrite(pin, HIGH);
	digitalWrite(pin, LOW);
}

void setupDDS(){
	pinMode(FQ_UD, OUTPUT);
	pinMode(W_CLK, OUTPUT);
	pinMode(DATA, OUTPUT);
	pinMode(RESET, OUTPUT);
	pulseHigh(RESET);
	pulseHigh(W_CLK);
	pulseHigh(FQ_UD);  // this pulse enables serial mode - Datasheet page 12 figure 10
}

void sendSerialMessage(){//Gets a string from the serial port, and send it out via morse code
	delay(10);
	char message[64];
	int length = 0;
  
	while(Serial.available() && length < 64){ message[length] = Serial.read(); length++; message[length] = '\0'; } transmitString(message); } void transmitString(char* message){ for(int i = 0; message[i] != '\0'; i++){ Serial.print(message[i]); transmitChar(message[i]); } Serial.println(); wordSpace(); } void transmitChar(char character){ int lookupValue; if(character > 64 && character < 91){ //Capital Letter (0-25) lookupValue = character - 65; } else if(character > 96 && character < 123){ //Lower Case Letter (0-25) lookupValue = character - 97; } else if(character > 47 && character < 58){ //Number (26-36) lookupValue = character - 48 + 26; } else if(character == 47){ // slash (37) lookupValue = 37; } else if(character == 32){ // space wordSpace(); return; } else{ return; //Invalid Character } byte length = (morseLookup[lookupValue] & B11100000) >> 5;
	byte pattern = morseLookup[lookupValue] & B00011111;
	byte mask = 1 << length-1;
	for(int i = 0; i < length; i++){ if(mask & morseLookup[lookupValue]){ dash(); } else{ dot(); } mask = mask >> 1;
	}
	charSpace();
}

void dot(){
	digitalWrite(pttOut,HIGH);
	sendFrequency(txfrequency);
	tone(pwmOut,toneFrequency);
	delay(1200/WPM);
	digitalWrite(pttOut,LOW);
	sendFrequency(0);
	noTone(pwmOut);
	delay(1200/WPM);
}

void dash(){
	digitalWrite(pttOut,HIGH);
	sendFrequency(txfrequency);
	tone(pwmOut,toneFrequency);
	delay(3 * 1200 / WPM);
	digitalWrite(pttOut,LOW);
	sendFrequency(0);
	noTone(pwmOut);
	delay(1200 / WPM);
}

void charSpace(){
	delay(2 * 1200 / WPM);
}

void wordSpace(){
	delay(7 * 1200/WPM);
}

More Features For The Repeater Database

repeaterdatabaseI’ve been spending a bit more time on the repeater database. It’s evolved from a simple html table with data from various sources, to a full-blown database-driven system that supports user editing and has more features than you can shake a stick at lexapro anxiety. You might ask, “Are all these features necessary?” and the answer is no. I didn’t do this to try and compete with some ham radio repeater websites. I just did it to learn about databases, and to have some fun, while getting a useful list of repeaters in the area. The database now has a google map for every repeater, as well as websites, and other information in every entry. It’s probably overkill, but like I said, it was a learning experience.

Edit (2015): I’ve decided to stop maintaining a repeater database of my own because there are now several good worldwide databases available with a large user base. The end result is a broad and high quality repeater database and it is available worldwide online.