[{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cu\u003eIntroduction\u003c/u\u003e\u003c/p\u003e\n\u003cp\u003eAstronomy is my primary hobby and taking photographs of night-sky objects is my particular interest.\u0026nbsp; A downside to this hobby is that it is very weather dependent.\u0026nbsp; If it’s cloudy nothing can be seen.\u0026nbsp; Weather reports are important to monitor but they just serve the general area.\u0026nbsp; The sky conditions at my specific location are better monitored with an AllSky Camera.\u003c/p\u003e\n\u003cp\u003eAn AllSky Camera is simply a camera with a fisheye lens that’s pointed up into the sky.\u0026nbsp; A program takes pictures of the sky all night long so checking the sky conditions can be done by looking at the latest sky image.\u0026nbsp; Is it too cloudy to take images?\u0026nbsp; Are clouds starting to move in?\u0026nbsp; Just check the AllSky Camera!\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/759/original/Allsky_Camera_and_Image.jpg?1711233671","metadata":{"caption":"AllSky Camera installed on Observatory  --  Sample AllSky Image with Milky Way"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cu\u003eThe Issue with Dew\u003c/u\u003e\u003c/p\u003e\n\u003cp\u003eUnfortunately, the dome of the AllSky Camera is prone to have dew forming on it when the humidity gets high.\u0026nbsp; Once that happens, the Allsky images are totally unusable.\u0026nbsp; To combat dew, many AllSky Cameras have a dew heater built into them.\u0026nbsp; The heater in my camera is very simple:\u0026nbsp; Apply 12 VDC and the heater is on.\u0026nbsp; Remove the voltage and the heater is off.\u0026nbsp; This applies about 10 W of power to the heater, and it does get hot enough to keep dew from forming on the dome.\u0026nbsp; Sometimes it gets too hot.\u003c/p\u003e\n\u003cp\u003eIf the humidity is moderate the 10 W of power is way more than needed to keep the dome clear.\u0026nbsp; An unwanted side effect of too much heat is that cameras don’t like it.\u0026nbsp; The hotter a camera gets the noisier, or grainer, its picture becomes.\u0026nbsp; This is especially apparent with long exposures and AllSky Cameras can take up to 60-second exposures under a dark sky!\u0026nbsp; The better solution is to vary the amount of power applied to the heater so that only enough is applied to keep dew from forming, and no more.\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cu\u003eThe Heater Controller\u003c/u\u003e\u003c/p\u003e\n\u003cp\u003eDesigning and building a heater controller that varies the heater power as needed was the answer.\u0026nbsp; Fortunately Adafruit provides most the parts needed for such a project.\u0026nbsp; In addition to a microcontroller, a humidity sensor was needed.\u0026nbsp; I chose to use a commercial wireless sensor that could be mounted outside the observatory.\u0026nbsp; A web page interface to monitor the status of the controller would be convenient.\u003c/p\u003e\n\u003cp\u003eThe Feather M0 Wifi microcontroller was a perfect fit.\u0026nbsp; It has the I/O to read data in, can produce a pulse-width-modulation (PWM) signal out and has a WiFi module that can host a web server.\u0026nbsp; The Acurite 592TXR “Tower Sensor” reads humidity and temperature and transmits the data on the 433 MHz ISM band.\u0026nbsp; To receive that data an inexpensive RXB12 433 MHz receiver module was selected.\u0026nbsp; Finally, a power MOSFET is used to control the current from a 12 VDC power supply to the heater.\u0026nbsp; All these devices are mounted on a half-size proto board.\u0026nbsp; Here’s a diagram of how it’s all connected.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/754/original/Heater_Controller_Diagram.jpg?1711212719","metadata":{"caption":"Connection Diagram of the Heater Controller"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cstrong\u003eParts available from Adafruit\u003c/strong\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/3044","metadata":{"name":"Feather M0 WiFi","url":"https://www.adafruit.com/product/3044","description":"This Feather is available in a few versions, select the most appropriate one.","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/355","metadata":{"name":"N-Channel Power MOSFET","url":"https://www.adafruit.com/product/355","description":"Controls the 12 VDC current sent to the heater based on the PWM signal.","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/1609","metadata":{"name":"Half-Size Perma-Proto Board","url":"https://www.adafruit.com/product/1609","description":"All the parts can be assembled onto this board.","quantity":"1","featured":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cstrong\u003eParts available from various online sources\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e(1) Acurite 592TXR Temperature and Humidity Sensor\u003c/p\u003e\n\u003cp\u003e(1) RBX12 433MHz Superheterodyne Receiver Module\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cspan style=\"text-decoration: underline;\"\u003ePutting it all Together\u003c/span\u003e\u003c/p\u003e\n\u003cp\u003eThe assembled controller was installed into a 3D printed enclosure so that it would be easy to handle.\u0026nbsp; The 12 VDC power supply plugs into the right jack and the heater cable plugs into the left jack.\u0026nbsp; A USB power supply provides power for the Feather and RXB12 receiver.\u0026nbsp; The temperature/humidity sensor and its 3D printed enclosure was installed on the north-facing wall of the observatory.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/755/original/Controller_and_Sensor_Sm.jpg?1711213361","metadata":{"caption":"Assembled Heater Controller    --     Installed Humidity/Temperature Sensor"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eWhen interrogated, the web server simply outputs two lines of text: a header and the current temperature in C and F, dew point, humidity and the percentage of heater power being applied.\u0026nbsp; The server connects to the home network so getting current data is easily done with a browser.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/756/original/06_Allsky_Htr_Web_Data_Sm.jpg?1711213653","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eMonitoring the weather conditions and the controller’s response over time was also desirable.\u0026nbsp; A python program was created that interrogates the web server every five minutes and produces a daily CSV formatted file that’s easily read into a spreadsheet.\u0026nbsp; Here’s the data recorded during a recent foggy night.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/757/original/07_Allsky_Htr_2024-03-09_Sm.jpg?1711213815","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe blue line shows the humidity level as the fog moved in and later dissipated.\u0026nbsp; The purple line shows the percentage of power that was applied to the heater.\u0026nbsp; It was experimentally determined that for my location, starting heater power at 70% humidity and ramping it up to 100% power at 95% humidity was effective at keeping dew off the Allsky Camera dome.\u0026nbsp; In this case the controller applied 100% heater power for only two hours of the entire fifteen-hour event!\u0026nbsp; That kept camera heating to a minimum and ensured that its images remained crisp and clean.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cstrong\u003eFeather M0 WiFi code:\u0026nbsp; AllSkyHtr433.ino\u003c/strong\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"/* AllSkyHtr433.ino 3/03/2024  Control Allsky heater with Wx data via 433 MHz\n * 433 MHz Wx reception code based from : Ray Wang (Rayshobby LLC)\n * Uses Adafruit Feather M0 WiFi, RXB6/RXB12 433 MHz receiver, MOSFET heater driver\n * Reads Wx data from an Acurite 592TXR Temperature/Humidity \"Tower\" sensor\n * Default PWM output of Feather M0 WiFi is at 733 Hz\n * Creates a web server to allow retreval of data in csv format\n */\n\n#include \u003cSPI.h\u003e\n#include \u003cWiFi101.h\u003e\n#include \"secrets.h\"    // contains WiFi network connection info\n\n// 433 reception defines\n#define RING_BUFFER_SIZE  256   // large enough to fit data between two successive syncs\n#define SYNC_LENGTH 2200\n#define SYNC_HIGH  600\n#define SYNC_LOW   600\n#define BIT1_HIGH  400\n#define BIT1_LOW   220\n#define BIT0_HIGH  220\n#define BIT0_LOW   400\n\n// heater function defines\n#define SENSOR_ID 43          // ID of Acurite Wx sensor to monitor\n#define DATAPIN 5             // Connection to output of 433 MHz receiver\n#define PWM_PIN 10            // PWM output to heater circuit\n#define HTR_ON_THRES 70       // turn heater on \u003e= humidity level\n#define HTR_MAX_THRES 95      // heater at 100% power \u003e= humidity level\n\n// 433 reception global variables\nunsigned long timings[RING_BUFFER_SIZE];\nunsigned int syncIndex1 = 0;  // index of the first sync signal\nunsigned int syncIndex2 = 0;  // index of the second sync signal\nbool received = false;\n\n// WiFi connection global variables\nchar ssid[] = SECRET_SSID;        // WiFi network SSID\nchar pass[] = SECRET_PASS;        // WiFi network password\n//IPAddress ip(HOST_IP);          // static IP address for WiFi board\nIPAddress ip(192,168,1,215);      // static IP address for WiFi board\nint wifiStatus = WL_IDLE_STATUS;\nbool wifiPresent = false;\nWiFiServer server(80);\n\n// Wx global variables\nint tempC = 0; int tempF = 0; int humidity = 0; int dewPoint = 0;   // variables from selected sensor\nint tC = 0; int tF = 0; int hum = 0; int dP = 0;                    // temporary variables from 433 receiver\nint pwmPercent  = 0;                                    // pwm value to drive heater power with\nfloat pwmScale = 100 / (HTR_MAX_THRES - HTR_ON_THRES);  // adjust for threshold changes\n\n//***** 433 MHz reception routines *****\n// detect if a sync signal is present\nbool isSync(unsigned int idx) {\n  // check if we've received 4 squarewaves of matching timing\n  int i;\n  for(i=0;i\u003c8;i+=2) {\n    unsigned long t1 = timings[(idx+RING_BUFFER_SIZE-i) % RING_BUFFER_SIZE];\n    unsigned long t0 = timings[(idx+RING_BUFFER_SIZE-i-1) % RING_BUFFER_SIZE];    \n    if(t0\u003c(SYNC_HIGH-100) || t0\u003e(SYNC_HIGH+100) ||\n       t1\u003c(SYNC_LOW-100)  || t1\u003e(SYNC_LOW+100)) {\n      return false;\n    }\n  }\n  // check if there is a long sync period prior to the 4 squarewaves\n  unsigned long t = timings[(idx+RING_BUFFER_SIZE-i)%RING_BUFFER_SIZE];\n  if(t\u003c(SYNC_LENGTH-400) || t\u003e(SYNC_LENGTH+400) ||\n    digitalRead(DATAPIN) != HIGH) {\n    return false;\n  }\n  return true;\n}\n\n/* Interrupt 1 handler */\nvoid handler() {\n  static unsigned long duration = 0;\n  static unsigned long lastTime = 0;\n  static unsigned int ringIndex = 0;\n  static unsigned int syncCount = 0;\n\n  // ignore if we haven't processed the previous received signal\n  if (received == true) {\n    return;\n  }\n  // calculating timing since last change\n  long time = micros();\n  duration = time - lastTime;\n  lastTime = time;\n\n  // store data in ring buffer\n  ringIndex = (ringIndex + 1) % RING_BUFFER_SIZE;\n  timings[ringIndex] = duration;\n\n  // detect sync signal\n  if (isSync(ringIndex)) {\n    syncCount ++;\n    // first time sync is seen, record buffer index\n    if (syncCount == 1) {\n      syncIndex1 = (ringIndex+1) % RING_BUFFER_SIZE;\n    } \n    else if (syncCount == 2) {\n      // second time sync is seen, start bit conversion\n      syncCount = 0;\n      syncIndex2 = (ringIndex+1) % RING_BUFFER_SIZE;\n      unsigned int changeCount = (syncIndex2 \u003c syncIndex1) ? (syncIndex2+RING_BUFFER_SIZE - syncIndex1) : (syncIndex2 - syncIndex1);\n      // changeCount must be 122 -- 60 bits x 2 + 2 for sync\n      if (changeCount != 122) {\n        received = false;\n        syncIndex1 = 0;\n        syncIndex2 = 0;\n      } \n      else {\n        received = true;\n      }\n    }\n  }\n}\n//***************************************\n\n\nvoid setup() {\n  // Configure the Feather ATWINC1500 connections\n  WiFi.setPins(8,7,4,2);\n\n  Serial.begin(9600);\n  delay(1000);\n  Serial.println(\"AllSkyHtr433 Started.\");\n\n  pinMode(DATAPIN, INPUT);    // put the 433 MHz data pin in input mode\n  attachInterrupt(digitalPinToInterrupt(DATAPIN), handler, CHANGE);\n  pinMode(PWM_PIN, OUTPUT);   // put the PWM pin in output mode\n  pinMode(13, OUTPUT);        // put the red LED pin in output mode\n  digitalWrite(13, LOW);      // turn the red LED off\n\n  // check for presence of onboard WiFi system:\n  if (WiFi.status() == WL_NO_SHIELD) {\n    wifiPresent = false;\n    Serial.println(\"\u003e\u003e Onboard WiFi system not detected \u003c\u003c\");\n  }\n  else {      // WiFi system is present to try to connect to it\n    wifiPresent = true;\n    WiFi.config(ip);                      // use this static IP address instead of DNS \n    while (wifiStatus != WL_CONNECTED) {  // this should abort if tried too many times\n      Serial.print(\"Attempting to connect to \"); Serial.println(ssid);\n      wifiStatus = WiFi.begin(ssid, pass);\n      delay(5000);                        // wait 5 seconds to allow for connection:\n    }\n    server.begin();\n    printWiFiStatus();                    // print out wifi status after connection\n  }\n}\n\nint t2b(unsigned int t0, unsigned int t1) {\n  if (t0\u003e(BIT1_HIGH-100) \u0026\u0026 t0\u003c(BIT1_HIGH+100) \u0026\u0026\n      t1\u003e(BIT1_LOW-100) \u0026\u0026 t1\u003c(BIT1_LOW+100)) {\n    return 1;\n  } else if (t0\u003e(BIT0_HIGH-100) \u0026\u0026 t0\u003c(BIT0_HIGH+100) \u0026\u0026\n             t1\u003e(BIT0_LOW-100) \u0026\u0026 t1\u003c(BIT0_LOW+100)){\n    return 0;\n  }\n  return -1;  // undefined\n}\n\nvoid loop() {\n  WiFiClient client = server.available(); // listen for a web client to connect\n  if (client) {         // service the web client connection\n    Serial.println(\"Web client connected\");\n    // an http request ends with a blank line\n    bool currentLineIsBlank = true;\n    while (client.connected()) {\n      if (client.available()) {\n        char c = client.read();\n        Serial.write(c);\n        if (c == '\\n' \u0026\u0026 currentLineIsBlank) {\n          // send a standard http response header\n          client.println(\"HTTP/1.1 200 OK\");\n          client.println(\"Content-Type: text/html\");\n          client.println(\"Connection: close\");  // the connection will be closed after completion of the response\n          //client.println(\"Refresh: 5\");  // refresh the page automatically every 5 sec\n          client.println();\n          client.println(\"\u003c!DOCTYPE HTML\u003e\");\n          client.println(\"\u003chtml\u003e\");\n          // output the web page content in csv format\n          client.print(\"TempC,TempF,DewPoint,Humidity,HtrPower\"); client.println(\"\u003cbr /\u003e\");\n          client.print(tempC); client.print(\",\"); client.print(tempF); client.print(\",\"); client.print(dewPoint); \n          client.print(\",\"); client.print(humidity); client.print(\",\"); client.print(pwmPercent); client.println(\"\u003cbr /\u003e\");\n          client.println(\"\u003c/html\u003e\");\n          break;\n        }\n      }\n    }\n    delay(1);         // give the web browser time to receive the data\n    client.stop();    // close the connection\n    Serial.println(\"client disconnected\");\n  }  // end of web client processing\n\n  if (received == true) {     // process the received 433 data\n    // disable interrupt to avoid new data corrupting the buffer\n    detachInterrupt(digitalPinToInterrupt(DATAPIN));\n    \n    unsigned int startIndex, stopIndex;\n    \n    // extract sensorID value\n    unsigned int sensorID = 0;\n    bool fail = false;\n    bool decodeFail = false;\n    startIndex = (syncIndex1 + (1*8+1)*2) % RING_BUFFER_SIZE;\n    stopIndex  = (syncIndex1 + (1*8+8)*2) % RING_BUFFER_SIZE;\n    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {\n      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);\n      sensorID = (sensorID\u003c\u003c1) + bit;\n      if (bit \u003c 0)  fail = true;                  // fail for this value\n    }\n    if (fail) {\n      Serial.print(\"sensorID decoding error: \");\n      Serial.println(sensorID);                   // print the erroneous value\n      decodeFail = true;                          // fail all values for this loop\n    }\n    else {\n      Serial.print(\"\\nSensorID: \");\n      Serial.println(sensorID);\n    }\n\n    // extract humidity value\n    hum = 0;\n    fail = false;\n    startIndex = (syncIndex1 + (3*8+1)*2) % RING_BUFFER_SIZE;\n    stopIndex =  (syncIndex1 + (3*8+8)*2) % RING_BUFFER_SIZE;\n    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {\n      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);\n      hum = (hum\u003c\u003c1) + bit;\n      if (bit \u003c 0)  fail = true;                  // fail for this value\n    }\n    if (fail) {\n      Serial.print(\"hum decoding error: \");\n      Serial.println(hum);                        // print the erroneous value\n      decodeFail = true;                          // fail all values for this loop\n    }\n    else {\n    if (hum \u003c 0) hum = 0;                         // ensure minimum limit\n    if (hum \u003e 100) hum = 100;                     // ensure maximum limit\n    Serial.print(hum); Serial.print(\",\");\n    }\n    \n    // extract temperature from two bytes\n    //unsigned long temp = 0;\n    int temp = 0;\n    fail = false;\n    // most significant 4 bits\n    startIndex = (syncIndex1 + (4*8+4)*2) % RING_BUFFER_SIZE;\n    stopIndex  = (syncIndex1 + (4*8+8)*2) % RING_BUFFER_SIZE;\n    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {\n      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);\n      temp = (temp\u003c\u003c1) + bit;\n      if (bit \u003c 0)  fail = true;                  // fail for this value     \n    }\n    // least significant 7 bits\n    startIndex = (syncIndex1 + (5*8+1)*2) % RING_BUFFER_SIZE;\n    stopIndex  = (syncIndex1 + (5*8+8)*2) % RING_BUFFER_SIZE;\n    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {\n      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);\n      temp = (temp\u003c\u003c1) + bit;\n      if (bit \u003c 0)  fail = true;                  // fail for this value      \n    }\n    if (fail) {\n      Serial.print(\"temp decoding error: \");\n      Serial.println(temp);                       // print the erroneous value\n      decodeFail = true;                          // fail all values for this loop\n    }\n    else {\n      tC = int((temp-1024)/10+1.9+0.5);           // round to the nearest integer\n      tF = int(tC*9/5+32+0.5);                    // round to the nearest integer\n      if (!decodeFail) dP = int((tC-(100-hum)/5)*9/5+32+0.5);    // simplistic approximation calculation   \n      Serial.print(tC); Serial.print(\",\"); Serial.print(tF); Serial.print(\",\"); Serial.println(dP);\n    } \n    // delay for 1 second to avoid repetitions\n    delay(1000);\n    received = false;\n    syncIndex1 = 0;\n    syncIndex2 = 0;\n\n    // Set Wx values for web page and output PWM signal to Heater\n    if (sensorID == SENSOR_ID \u0026\u0026 !decodeFail) {                   // only use selected sensor\n      tempC = tC; tempF = tF; dewPoint = dP; humidity = hum;      // save values from selected sensor\n      pwmPercent = int((humidity - HTR_ON_THRES) * pwmScale);     // using measured humidity\n      //pwmPercent = int(random(0,100));                          // using random humidity for testing\n      if (pwmPercent \u003c 0) pwmPercent = 0;                         // ensure lower limit\n      if (pwmPercent \u003e HTR_MAX_THRES) pwmPercent = 100;           // ensure upper limit\n      Serial.print(\"Set PWM to \"); Serial.print(pwmPercent); Serial.print(\" on pin \"); Serial.println(PWM_PIN);\n      analogWrite(PWM_PIN, int(pwmPercent*2.55));                 // scale to 0-255 range needed by analogWrite\n    }\n\n    blink13(25);  // blink the red LED when 433 MHz data is received\n\n    // re-enable interrupt\n    attachInterrupt(digitalPinToInterrupt(DATAPIN), handler, CHANGE);\n  }\n\n}\n\nvoid printWiFiStatus() {\n  // print the SSID of the network the board is attached to:\n  Serial.print(\"Connected to SSID: \"); Serial.println(WiFi.SSID());\n\n  // print the onboard WiFi system IP address:\n  IPAddress ip = WiFi.localIP();\n  Serial.print(\"Board's IP Address: \"); Serial.println(ip);\n\n  // print the received signal strength:\n  long rssi = WiFi.RSSI();\n  Serial.print(\"signal strength (RSSI):\"); Serial.print(rssi); Serial.println(\" dBm\");\n}\n\nvoid blink13(int mS){\n  digitalWrite(13, HIGH);     // turn LED on\n  delay(mS);                  // wait time in mSec\n  digitalWrite(13, LOW);      // turn LED off\n}","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cstrong\u003eSecrets.h contents:\u003c/strong\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"#define SECRET_SSID \"MyNetwork\"\n#define SECRET_PASS \"MyPassword\"\n#define HOST_IP \"192,168,1,215\"","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cstrong\u003ePython CSV recording code:\u0026nbsp; GetAllskyWxData.py\u003c/strong\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# GetAllskyWxData.py  2024-02-27\n\nimport time, os, urllib.request\nfrom time import strftime\n\nhtrURL = \"http://192.168.1.215\"         # feather web server IP\ndataPath = \"D:/Astronomy/AllSky/Wx/\"    # location to store data files\n\nwhile True:\n    nowDate = strftime(\"%m/%d/%Y\",time.localtime())\n    nowTime = strftime(\"%H:%M:%S\",time.localtime())\n\n    with urllib.request.urlopen(htrURL) as f:   # open web page\n        lines = f.readlines()                   # read all lines of page\n        header = str(lines[2])                  # extract header line\n        header = header[header.find('T'):header.find('\u003c')]  # parse it\n        header = \"Date,Time,\" + header          # add Date, Time\n        data = str(lines[3])                    # extract data line\n        data = data[data.find(\"'\")+1:data.find('\u003c')]    # parse it\n\n    # set data csv filename based on date\n    dataFile = strftime(\"%Y%m%d\",time.localtime()) + \".csv\"\n    if not os.path.exists(dataPath+dataFile):   # if data file doesn't exist\n        f = open(dataPath+dataFile, 'a')        # create it with header\n        f.write(header+'\\n')\n        f.write(nowDate+\",\"+nowTime+\",\"+data+'\\n')\n        f.close()\n    else:\n        f = open(dataPath+dataFile, 'a')        # the data file does exist\n        f.write(nowDate+\",\"+nowTime+\",\"+data+'\\n')  # so just add new data\n        f.close()\n    print(nowDate+\",\"+nowTime+\",\"+data)\n    time.sleep((5*60)-1)                        # pause for 5 minutes","metadata":{"language":"auto","linenums":false}}]