Last active
June 27, 2023 17:50
-
-
Save zerog2k/00bad44d59bcd7937420 to your computer and use it in GitHub Desktop.
acurite 5n1 weather station decode over 433 mhz ask rx module on esp8266
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
// acurite 5n1 weather station (VN1TXC) | |
// decoding over 433MHz ASK superhet RX module | |
// on ESP8266 SDK | |
// | |
// Jens Jensen, 2015 | |
// | |
// todo: emit wx packets as json | |
// todo: add webserver code | |
*/ | |
#include "ets_sys.h" | |
#include "osapi.h" | |
#include "gpio.h" | |
#include "os_type.h" | |
#include "user_config.h" | |
#include "user_interface.h" | |
// pulse timings | |
// SYNC | |
#define SYNC_HI 675 | |
#define SYNC_LO 550 | |
#define HIGH 1 | |
#define LONG_HI 475 | |
#define LONG_LO 350 | |
#define LOW 0 | |
#define SHORT_HI 275 | |
#define SHORT_LO 150 | |
#define RESETTIME 10000 | |
// other settables | |
#define MAXBITS 65 // max framesize | |
#define DATAPIN 12 // GPIO12 | |
#define DATAPINBIT BIT12 | |
//#define DATAPINID (GPIO_ID_PIN(DATAPIN)) | |
#define DEBUG 1 // uncomment to enable debugging | |
#define DEBUGPIN 5 // gpio5 | |
#define METRIC_UNITS 0 // select display of metric or imperial units | |
// sync states | |
#define RESET 0 // no sync yet | |
#define INSYNC 1 // sync pulses detected | |
#define SYNCDONE 2 // complete sync header received | |
volatile uint8_t pulsecnt = 0; | |
volatile uint32_t risets = 0; // track rising edge time | |
volatile uint32_t syncpulses = 0; // track sync pulses | |
volatile uint8_t state = RESET; | |
volatile uint8_t buf[8] = {0,0,0,0,0,0,0,0}; // msg frame buffer | |
volatile bool reading = false; // have valid reading | |
uint32_t raincounter = 0; | |
// wind directions: | |
// { "NW", "WSW", "WNW", "W", "NNW", "SW", "N", "SSW", | |
// "ENE", "SE", "E", "ESE", "NE", "SSE", "NNE", "S" }; | |
const float winddirections[] = { 315.0, 247.5, 292.5, 270.0, | |
337.5, 225.0, 0.0, 202.5, | |
67.5, 135.0, 90.0, 112.5, | |
45.0, 157.5, 22.5, 180.0 }; | |
// wx message types | |
#define MT_WS_WD_RF 49 // wind speed, wind direction, rainfall | |
#define MT_WS_T_RH 56 // wind speed, temp, RH | |
// some esp8266 stuff | |
#define user_procTaskPrio 0 | |
#define user_procTaskQueueLen 1 | |
os_event_t user_procTaskQueue[user_procTaskQueueLen]; | |
static void loop(os_event_t *events); | |
bool crc(volatile uint8_t row[], int cols); | |
void My_ISR(); | |
void gpio_init(); | |
uint32_t RTCCAL; | |
void ICACHE_FLASH_ATTR gpio_init() { | |
ETS_GPIO_INTR_DISABLE(); // Disable gpio interrupts | |
ETS_GPIO_INTR_ATTACH(My_ISR, DATAPIN); // GPIO12 interrupt handler | |
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); // Set GPIO12 function | |
PIN_PULLDWN_DIS(PERIPHS_IO_MUX_MTDI_U); | |
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDI_U); | |
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5); | |
PIN_PULLDWN_DIS(PERIPHS_IO_MUX_GPIO5_U); | |
PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO5_U); | |
gpio_output_set(0, 0, DATAPINBIT, DATAPINBIT); // Set GPIO12 as input, GPIO5 as output | |
GPIO_OUTPUT_SET(DEBUGPIN, HIGH); | |
gpio_pin_intr_state_set(DATAPIN, GPIO_PIN_INTR_ANYEDGE); // Interrupt on any GPIO12 edge | |
ETS_GPIO_INTR_ENABLE(); // Enable gpio interrupts | |
} | |
void My_ISR() | |
{ | |
// Disable gpio interrupts | |
ETS_GPIO_INTR_DISABLE(); | |
//clear interrupt status | |
uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); | |
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); | |
// decode the pulses | |
uint32_t timestamp = system_get_time(); | |
//uint32_t timestamp = system_get_rtc_time() * system_rtc_clock_cali_proc() >> 12; | |
if (GPIO_INPUT_GET(DATAPIN) == HIGH) { | |
if (timestamp - risets > RESETTIME) { | |
// detect reset condition | |
state=RESET; | |
syncpulses=0; | |
pulsecnt=0; | |
} | |
risets = timestamp; | |
ETS_GPIO_INTR_ENABLE(); | |
return; | |
} | |
/* | |
if (reading) { | |
// no-op til we print last result | |
ETS_GPIO_INTR_ENABLE(); | |
return; | |
} | |
*/ | |
// going low | |
uint32_t duration = timestamp - risets; | |
if (state == RESET || state == INSYNC) { | |
// looking for sync pulses | |
if (SYNC_LO < duration && duration < SYNC_HI) { | |
// start counting sync pulses | |
state=INSYNC; | |
syncpulses++; | |
if (syncpulses > 3) { | |
// found complete sync header | |
state = SYNCDONE; | |
syncpulses = 0; | |
pulsecnt=0; | |
#ifdef DEBUG | |
// quick debug to trigger logic analyzer at sync | |
GPIO_OUTPUT_SET(DEBUGPIN, LOW); | |
#endif | |
} | |
} else { | |
// not interested, reset | |
syncpulses=0; | |
pulsecnt=0; | |
state=RESET; | |
#ifdef DEBUG | |
GPIO_OUTPUT_SET(DEBUGPIN, HIGH); //return trigger | |
#endif | |
} | |
} else { | |
// SYNCDONE, now look for message | |
// detect if finished here | |
if ( pulsecnt > MAXBITS ) { | |
state = RESET; | |
pulsecnt = 0; | |
reading = true; | |
#ifdef DEBUG | |
GPIO_OUTPUT_SET(DEBUGPIN, HIGH); //return trigger | |
#endif | |
ETS_GPIO_INTR_ENABLE(); // Enable gpio interrupts | |
return; | |
} | |
// stuff buffer with message | |
uint8_t bytepos = pulsecnt / 8; | |
uint8_t bitpos = pulsecnt % 8; | |
if ( LONG_LO < duration && duration < LONG_HI) { | |
buf[bytepos] |= (0x80 >> bitpos); // bit set | |
pulsecnt++; | |
} else if ( SHORT_LO < duration && duration < SHORT_HI) { | |
buf[bytepos] &= ~ (0x80 >> bitpos); // bit clear | |
pulsecnt++; | |
} | |
} | |
ETS_GPIO_INTR_ENABLE(); // Enable gpio interrupts | |
} | |
bool crc(volatile uint8_t row[], int cols) { | |
// sum of first n-1 bytes modulo 256 should equal nth byte | |
cols -= 1; // last byte is CRC | |
int sum = 0; | |
uint8_t i; | |
for (i = 0; i < cols; i++) { | |
sum += row[i]; | |
} | |
if (sum != 0 && sum % 256 == row[cols]) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
float getTempF(uint8_t hibyte, uint8_t lobyte) { | |
// range -40 to 158 F | |
int highbits = (hibyte & 0x0F) << 7; | |
int lowbits = lobyte & 0x7F; | |
int rawtemp = highbits | lowbits; | |
float temp = (rawtemp - 400) / 10.0; | |
return temp; | |
} | |
float getWindSpeed(uint8_t hibyte, uint8_t lobyte) { | |
// range: 0 to 159 kph | |
int highbits = (hibyte & 0x7F) << 3; | |
int lowbits = (lobyte & 0x7F) >> 4; | |
float speed = highbits | lowbits; | |
// speed in m/s formula according to empirical data | |
if (speed > 0) { | |
speed = speed * 0.23 + 0.28; | |
} | |
float kph = speed * 60 * 60 / 1000; | |
return kph; | |
} | |
float getWindDirection(uint8_t b) { | |
// 16 compass points, ccw from (NNW) to 15 (N), | |
// { "NW", "WSW", "WNW", "W", "NNW", "SW", "N", "SSW", | |
// "ENE", "SE", "E", "ESE", "NE", "SSE", "NNE", "S" }; | |
int direction = b & 0x0F; | |
return winddirections[direction]; | |
} | |
int getHumidity(uint8_t b) { | |
// range: 1 to 99 %RH | |
int humidity = b & 0x7F; | |
return humidity; | |
} | |
int getRainfallCounter(uint8_t hibyte, uint8_t lobyte) { | |
// range: 0 to 99.99 in, 0.01 increment rolling counter | |
// TODO: implement counter rollover | |
int raincounter = ((hibyte & 0x7f) << 7) | (lobyte & 0x7F); | |
return raincounter; | |
} | |
float convKphMph(float kph) { | |
return kph * 0.62137; | |
} | |
float convFC(float f) { | |
return (f-32) / 1.8; | |
} | |
float convInMm(float in) { | |
return in * 25.4; | |
} | |
int power(int base, int exp){ | |
int result = 1; | |
while(exp) { result *= base; exp--; } | |
return result; | |
} | |
static char* ftoa(float num, uint8_t decimals) { | |
// float to string; no float support in esp8266 sdk printf | |
// warning: limited to 15 chars & non-reentrant | |
// e.g., dont use more than once per os_printf call | |
static char* buf[16]; | |
int whole = num; | |
int decimal = (num - whole) * power(10, decimals); | |
if (decimal < 0) { | |
// get rid of sign on decimal portion | |
decimal -= 2 * decimal; | |
} | |
char* pattern[10]; // setup printf pattern for decimal portion | |
os_sprintf(pattern, "%%d.%%0%dd", decimals); | |
os_sprintf(buf, pattern, whole, decimal); | |
return (char *)buf; | |
} | |
void parsewx() { | |
bool crcpass = crc(buf, sizeof(buf)); | |
#ifdef DEBUG | |
os_printf("%02X %02X %02X %02X %02X %02X %02X %02X CRC %s\n", | |
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7], | |
crcpass ? "PASS" : "FAIL"); | |
#endif | |
if (crcpass) { | |
// passes crc, good message | |
float windspeedkph = getWindSpeed(buf[3], buf[4]); | |
if (METRIC_UNITS) { | |
os_printf("windspeed: %s km/h, ", ftoa(windspeedkph,1)); | |
} else { | |
os_printf("windspeed: %s mph, ", ftoa(convKphMph(windspeedkph),1)); | |
} | |
uint8_t msgtype = (buf[2] & 0x3F); | |
if (msgtype == MT_WS_WD_RF) { | |
// wind speed, wind direction, rainfall | |
float rainfall = 0.00; | |
unsigned int curraincounter = getRainfallCounter(buf[5], buf[6]); | |
if (raincounter > 0) { | |
// track rainfall difference after first run | |
rainfall = (curraincounter - raincounter) * 0.01; | |
} else { | |
// capture starting counter | |
raincounter = curraincounter; | |
} | |
float winddir = getWindDirection(buf[4]); | |
os_printf("wind direction: %s", ftoa(winddir,1)); | |
if (METRIC_UNITS) { | |
os_printf(", rain gauge: %s mm", ftoa(convInMm(rainfall),1)); | |
} else { | |
os_printf(", rain gauge: %s inches", ftoa(rainfall,2)); | |
} | |
os_printf("\n"); | |
} else if (msgtype == MT_WS_T_RH) { | |
// wind speed, temp, RH | |
float tempf = getTempF(buf[4], buf[5]); | |
uint8_t humidity = getHumidity(buf[6]); | |
bool batteryok = ((buf[2] & 0x40) >> 6); | |
if (METRIC_UNITS) { | |
os_printf("temp: %s C, ", ftoa(convFC(tempf),1)); | |
} else { | |
os_printf("temp: %sF, ", ftoa(tempf,1)); | |
} | |
os_printf("humidity: %d %%RH, ", humidity); | |
if (batteryok) { | |
os_printf("battery: OK"); | |
} else { | |
os_printf("battery: LOW"); | |
} | |
os_printf("\n"); | |
} else { | |
os_printf("unknown msgtype\n"); | |
} | |
} | |
} | |
// setup | |
void ICACHE_FLASH_ATTR user_init() | |
{ | |
char ssid[32] = SSID; | |
char password[64] = SSID_PASSWORD; | |
uint8_t mac[6] = {0x18, 0xfe, 0x34, 0x9b, 0xd3, 0xb9}; | |
struct station_config stationConf; | |
//Set station mode | |
wifi_set_opmode( STATION_MODE ); | |
//Set ap settings | |
os_memcpy(&stationConf.ssid, ssid, 32); | |
os_memcpy(&stationConf.password, password, 64); | |
//os_memcpy(&stationConf.bssid, mac, 6); | |
//os_memset(&stationConf.bssid_set, 0); | |
wifi_station_set_config(&stationConf); | |
wifi_station_set_auto_connect(0); | |
// Initialize UART | |
uart_div_modify(0, UART_CLK_FREQ / 115200); | |
//setup gpio interrupts | |
gpio_init(); | |
// store rtc calibration value | |
RTCCAL = system_rtc_clock_cali_proc() >> 12; | |
// turn off system printing to uart0 | |
//system_set_os_print(0); | |
//Start loop | |
system_os_task(loop, user_procTaskPrio,user_procTaskQueue, user_procTaskQueueLen); | |
system_os_post(user_procTaskPrio, 0, 0 ); | |
} | |
// main loop | |
static void ICACHE_FLASH_ATTR loop(os_event_t *events) | |
{ | |
os_printf("."); | |
if (reading) { | |
ETS_GPIO_INTR_DISABLE(); | |
os_printf("\n"); | |
parsewx(); // parse and output wx data | |
reading = false; | |
ETS_GPIO_INTR_ENABLE(); | |
} | |
/* testing ftoa | |
float test1 = 233.0567; | |
os_printf("floattest1: %s\n", ftoa(test1, 4)); | |
*/ | |
os_delay_us(2000000); | |
system_os_post(user_procTaskPrio, 0, 0 ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment