Skip to content

Instantly share code, notes, and snippets.

@zerog2k
Last active June 27, 2023 17:50
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save zerog2k/00bad44d59bcd7937420 to your computer and use it in GitHub Desktop.
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
/*
// 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