Author: <span>Brad Morse</span>

Datalogger / Flight Recorder

One feature of most commercial altimeters is to record a variety of data during flight, such as temperature, altitude, speed, angle, etc..

Of course, to accomplish this, some persistent non-volatile storage is needed.  A MicroSD card seems like it would be a reasonable solution, it has a large storage capacity relative to the information being recorded.  MicroSD cards are very small and durable.

There are some limitations due to the Arduino hardware itself, and within libraries that I’ve found that the interface for MicroSD will leave some to be desired.

 

sketchbook.ino

datalogger.h

#include <SD.h>;

class Datalogger {

public:

  Datalogger(int sd_pin, char filename[]);

  void println(const char message[]);
  
  void print(const char message[]);
  void print(const char c);

  bool isReady();
  bool tryOpen();
  void setFilename(char next[]);
  char* getFilename();
  void roll(int year, int month, int day, int hour, int minute);
  void disable();
  void close();
  

private:
//  char* filename;
  char filename[15];
//  String _file;
  int sd_pin;
  int sequence = 100;
  bool disabled = false;
  bool open = false;
  bool ready = false;
  File dataFile;

};

datalogger.cpp

#include "datalogger.h"
#include <SD.h>

const String TXT = String(".txt");
const char* NOT_READY = "SD not ready!";

Datalogger::Datalogger(int sd_pin, char filename[]) {
  this->sd_pin = sd_pin;
  setFilename(filename);
}


void Datalogger::roll(int year, int month, int day, int hour, int minute) {
  if (year == 0) {
    return;
  }
  char* name = new char[10];
  sprintf(name, "%02d%02d%02d%02d", month, day, hour, minute);
  name[9] = 0;
  setFilename(name);
  free(name);
}


char* Datalogger::getFilename() {
  return filename;
}

void Datalogger::setFilename(char id[]) {
  if (dataFile != NULL && dataFile) {
    dataFile.close();
  }
  int i = 0;
  for (; i < 10 && id[i] != 0; i++) {
    filename[i] = id[i];
    filename[i + 1] = 0;
  }
  filename[i++] = '.';
  filename[i++] = 'T';
  filename[i++] = 'X';
  filename[i++] = 'T';
  filename[i] = 0;

  if (Serial) Serial.println(filename);
  if (ready = tryOpen()) {

  } else {
    if (Serial) Serial.println("SD Error");

  }
}


void Datalogger::disable() {
  disabled = true;
}

bool Datalogger::isReady() {
  if (disabled) return false;
  if (ready) return true;
  return ready = SD.begin(sd_pin);  // D10 usually
}

bool Datalogger::tryOpen() {
  if (disabled) return false;

  if (!ready && !isReady()) {
    return false;
  }

  if (dataFile == NULL || dataFile == false) {
    dataFile = SD.open(filename, FILE_WRITE);
  }
  return open = dataFile == true;
}

void Datalogger::close() {
  open = false;
  if (disabled || !ready || dataFile == NULL) {
    return;
  }

  dataFile.close();
}

void Datalogger::print(const char c) {

  if (disabled) return;

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  // File dataFile = SD.open(filename, FILE_WRITE);

  if (open || tryOpen()) {
    dataFile.print(c);

        if (Serial) Serial.print(c);

  } else {
    Serial.println(NOT_READY);
    ready = false;
  }

}

void Datalogger::print(const char message[]) {

  if (disabled) return;

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  // File dataFile = SD.open(filename, FILE_WRITE);

  if (open || tryOpen()) {
    dataFile.print(message);

        if (Serial) Serial.print(message);

  } else {

    Serial.println(NOT_READY);
    ready = false;
  }

}

void Datalogger::println(const char message[]) {

  if (disabled) return;

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  // File dataFile = SD.open(filename, FILE_WRITE);

  if (open || tryOpen()) {
    dataFile.println(message);
        if (Serial) Serial.println(message);

  } else {

    Serial.println(NOT_READY);
    ready = false;
  }

}

Cubecell HTCC-AB02S

CubeCell HTCC-AB02S

HTCC-AB02S is a Dev-Board. Already integrated AIR530Z GPS module, friendly designed for developers, easy to verify communication solutions.


I found this board by browsing around for an integrated solution that has GPS, Radio, and display capabilities. Getting the libraries working took some effort, but it has been worth it.

The CubeCell HTCC-AB02S is LoRa capable out-of-the-box, and eventually I will invest more time in understanding the proper usage of the protocol. In my project I took a very naive approach, please be advised this is probably not an optimal solution.

I purchased two of these boards, one to act as a beacon/transmitter that chirps out it’s current GPS coordinates every few seconds. The second board I programmed as a receiver and it computes the distance and bearing to the beacon and displays information on the LCD screen.

Product Link

ezradio.h
#include "Arduino.h"
#include "LoRaWan_APP.h"

#define LORA_PREAMBLE_LENGTH                8               // Same for Tx and Rx
#define LORA_FIXED_PAYLOAD                  false
#define LORA_CRC                            true
#define TX_OUTPUT_POWER                     20 // 14        // dBm (max = 22).
#define LORA_SYMBOL_TIMEOUT                 0               // Symbols
#define LORA_USE_FIXED_PAYLOAD           false
#define LORA_IQ_INVERSION                false
#define LORA_FREQ_HOP                    false

// radio transmission data
struct RadioPacket {
  int16_t id;
  uint8_t from;
  uint8_t type;
  int16_t rssi;
  int8_t snr;
  uint8_t signalStrength;
  uint32_t at;
  double lat;
  double lon;
  uint32_t age;
  int length;
  char data[64];
};

struct EzRadioCfg {
  RadioModems_t modem = MODEM_LORA;
  int8_t power = 18;  // max 22

  uint8_t fdev = 0;  // FSK Modem Only. Sets the frequency deviation (FSK only)

  // LORA_BANDWIDTH
  //  0: 125 kHz,
  //  1: 250 kHz,
  //  2: 500 kHz,
  //  3: Reserved
  byte bandwidth = 0;

  // LORA_SPREADING_FACTOR
  // min/default 7, max 12          [SF7..SF12]
  byte datarate = 7;

  // LORA_CODINGRATE
  //  1: 4/5,
  //  2: 4/6,
  //  3: 4/7,
  //  4: 4/8
  uint8_t coderate = 1;

  uint16_t preambleLen = LORA_PREAMBLE_LENGTH;
  bool fixLen = LORA_FIXED_PAYLOAD;
  bool crcOn = LORA_CRC;

  // Frequency Hop
  // Enables disables the intra-packet frequency hopping
  bool freqHop = LORA_FREQ_HOP;
  // Number of symbols between each hop
  uint8_t hopPeriod = 0;

  bool iqInverted = false;
  uint32_t timeout = 3000;  // milliseconds
};


class EzRadio {

  public:

    RadioEvents_t* handler;
    EzRadioCfg cfg;

    EzRadio(uint8_t uuid, RadioEvents_t* handler, EzRadioCfg cfg);

    void reconfigure();

    void transmit(uint16_t id, uint8_t type, char* data);


    void ack(uint16_t id, uint8_t from, uint8_t sig);
    bool waitForAck(uint16_t id);

    void ping(uint16_t id, uint8_t client);

    bool read(RadioPacket* message);
    bool read(RadioPacket* message, uint16_t timeout);

  private:
    uint8_t uuid;
    void OnTxDone( void );
    void OnTxTimeout( void );

};




EzRadio* eZRadio_configure(uint8_t uuid, RadioEvents_t* events, EzRadioCfg cfg);

void eZRadioReceiveSignal(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
ezradio.cpp
#include "ezradio.h"

static RadioPacket RECEIVED;

static bool eZRadioMessageReady = false;
static bool eZRadioReadingMessage = false;

static uint32_t eZRadio_lastTx = 0;
static uint32_t eZRadio_lastRx = 0;

const uint8_t SYM_5 = 0x7b;

const uint8_t HDR_SIZE = 8; // + sizeof(double) * 2;

void eZRadioReceiveSignal( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) {

  // message structure
  //
  // first two bytes = 18 bits of uint16_t message sequence id
  // third byte = 8 bits of client identifier
  //
  eZRadioMessageReady = false;

  if (size < HDR_SIZE || payload[5] != SYM_5 || payload[6] != 0x8c || payload[7] != ':') {
    // invalid message, throw it away
    for (int i = 0; i < 3; i++) {
      // flash the LED red on error reading messages
      turnOnRGB(0x550000, 120);
      turnOnRGB(0, 100);
    }
    return;
  }

  eZRadioReadingMessage = true;
  turnOnRGB(0x000500, 0);

  RECEIVED.at = millis();
  RECEIVED.id = (uint16_t)payload[0] | ((uint16_t)payload[1] << 8); // (uint16_t)((uint16_t*)payload)[0];
  RECEIVED.from = (short) payload[2] & 0x0F;
  RECEIVED.type = payload[3];
  RECEIVED.rssi = rssi;
  RECEIVED.snr = snr;
  RECEIVED.length = size - HDR_SIZE;

  RECEIVED.signalStrength = 100 + (min(-30, max(-130, rssi)) + 30);

  memcpy(RECEIVED.data, &payload[HDR_SIZE], RECEIVED.length);

  switch (payload[3]) {
    case '1': // ack

      break;
    case '2': // ping


    case 'P': // position
      char readBuffer[16];
      int i = HDR_SIZE;

      for (; i + 1 < size && payload[i] != ' '; i++) {
        readBuffer[i - HDR_SIZE] = payload[i];
      }
      readBuffer[i] = 0;

      RECEIVED.lat = String(readBuffer).toDouble();

      int k = i + 1;
      for (i = k; i + 1 < size && payload[i] != 0; i++) {
        readBuffer[i - k] = payload[i];
      }
      readBuffer[i] = 0;

      RECEIVED.lon = String(readBuffer).toDouble();
      break;
  }
  Serial.printf("receiving size=%d msg='%s'\n", size, &payload[HDR_SIZE]);


  eZRadio_lastRx = millis();
  eZRadioReadingMessage = false;
  eZRadioMessageReady = true;

  Radio.Rx(0);
  turnOnRGB(0, 0);
}


EzRadio::EzRadio(uint8_t uuid, RadioEvents_t* handler, EzRadioCfg cfg) {
  this->uuid = uuid;
  this->handler = handler;
  this->cfg = cfg;
  handler->RxDone = eZRadioReceiveSignal;
  Radio.Init(handler);
}

void EzRadio::reconfigure() {

  Radio.SetTxConfig( cfg.modem,
                     cfg.power,
                     0,
                     cfg.bandwidth,
                     cfg.datarate,
                     cfg.coderate,
                     cfg.preambleLen,
                     cfg.fixLen,
                     cfg.crcOn,
                     cfg.freqHop,
                     cfg.hopPeriod,
                     LORA_IQ_INVERSION, 3000
                   );

  Radio.SetRxConfig( cfg.modem,
                     cfg.bandwidth,
                     cfg.datarate,
                     cfg.coderate,
                     0, // bandwidthAfc
                     cfg.preambleLen,
                     LORA_SYMBOL_TIMEOUT,
                     cfg.fixLen,
                     64, // uint8_t payloadLen (if fixLen == true)
                     cfg.crcOn,
                     cfg.freqHop,
                     cfg.hopPeriod,
                     LORA_IQ_INVERSION,
                     true // contiunous
                   );

}

void EzRadio::OnTxDone( void ) {

}
void EzRadio::OnTxTimeout( void ) {

}


void EzRadio::ack(uint16_t id, uint8_t from, uint8_t sig) {
  byte channel = cfg.bandwidth;
  transmit(id, '1', "ACK");
}

void EzRadio::ping(uint16_t id, uint8_t client) {
  transmit(id, '2', "PING");
}

void EzRadio::transmit(uint16_t id, uint8_t type, char* data) {
  turnOnRGB(0x070000, 10);

  int size = strlen(data);
  char* txpacket = new char[size + HDR_SIZE];

  txpacket[0] = (uint8_t) (id & 0xFF);  // low bits
  txpacket[1] = (uint8_t) (id >> 8); // high bits


  txpacket[2] = uuid;
  txpacket[3] = type;
  txpacket[4] = 0x2a; // reserved
  txpacket[5] = SYM_5; // reserved
  txpacket[6] = 0x8c; // reserved
  txpacket[7] = ':';


  sprintf(&txpacket[HDR_SIZE], data, size);
  txpacket[size + HDR_SIZE] = 0;

  Serial.printf("\r\nsending packet \"%s\" , length %d\r\n", &txpacket[HDR_SIZE], HDR_SIZE + size);
  Radio.Send( (uint8_t *)txpacket, size + HDR_SIZE);
  eZRadio_lastTx = millis();

  turnOnRGB(0, 0);
  free(txpacket);
}

bool EzRadio::read(RadioPacket* message) {
  return read(message, 0);
}

bool EzRadio::waitForAck(uint16_t msgId) {
  Radio.Rx(100);
  RadioPacket ack;
  if (read(&ack, 100)) {
    return ack.type == '1' && msgId == ack.id;
  }
  return false;
}

bool EzRadio::read(RadioPacket* message, uint16_t timeout) {
  timeout += millis();
  while ((!eZRadioMessageReady || eZRadioReadingMessage) && timeout > millis()) {
    delay(20); // reading a message now
  }

  if (!eZRadioMessageReady) {
    return false;
  }

  message->at = RECEIVED.at;
  message->id = RECEIVED.id;
  message->from = RECEIVED.from;
  message->rssi = RECEIVED.rssi;
  message->snr = RECEIVED.snr;
  message->signalStrength = RECEIVED.signalStrength;
  message->length = RECEIVED.length;
  memcpy(message->data, RECEIVED.data, RECEIVED.length);
  message->lat = RECEIVED.lat;
  message->lon = RECEIVED.lon;
  message->age = millis() - RECEIVED.at;
  message->type = RECEIVED.type;

  eZRadioMessageReady = false;
  return true;
  ;
}


EzRadio* eZRadio_configure(uint8_t uuid, RadioEvents_t* events, EzRadioCfg cfg) {
  EzRadio* ez = new EzRadio(uuid, events, cfg);
  ez->reconfigure();
  return ez;
}

Joystick Example

sketchbook.ino

#include "thumbstick.h"

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 6;    // switch to turn on and off mouse control
const int xAxis = A0;       // joystick X axis pin
const int yAxis = A1;       // joystick Y axis pin

class MenuListener {
  public:
  bool press();
  void release(const int ms);
};

bool MenuListener::press() {
  return true;
}

void MenuListener::release(const int ms) {

  if (ms > 7000) {
    // system reset
    Serial.println("RESET");
  } else if (ms > 3000) {
    // extra
    Serial.println("Extra");
  } else {
    // normal click release, take some action.
    Serial.println("Select");
  }
}

// Singleton instances
MenuListener menuListener = MenuListener();
ThumbstickButton button = ThumbstickButton(6, xAxis, yAxis, (PressButton)&menuListener.press, (ReleaseButton)&menuListener.release);

void setup() {

  Serial.begin(115200);

}

void loop() {

  if (button.pushed()) {

  }
  
  ThumbstickPosition p = button.position();

  if (p.x != 0 || p.y != 0) {
    char jxy[30];
    sprintf(jxy, "%i,%i %i,%i\0", p.x, p.y, p.dx, p.dy);
    Serial.println(jxy);
  }
  
  delay(5);
  
}
thumbstick.h
#include "Arduino.h"

typedef bool (*PressButton) ();
typedef void (*ReleaseButton) (const int ms);

class ThumbstickPosition {
  public:
    int x = 0;
    int y = 0;
    int dx = 0;
    int dy = 0;
    
    ThumbstickPosition();
    ThumbstickPosition(const int x, const int y, const int dx, const int dy);

  private:
  
    bool motion = false;
};

class ThumbstickButton {
  public:

    ThumbstickButton(const int pin, const int xAxis, const int yAxis, PressButton btnPress, ReleaseButton btnRelease);
    bool pushed();
    unsigned long age();
    ThumbstickPosition position();
  
  private:
    int pin;
    int xAxis;
    int yAxis;
    
    unsigned long since = 0;
    PressButton btnPress;
    ReleaseButton btnRelease;
    ThumbstickPosition positions[2];
    
    bool pressed = false;
    int8_t peek = 0;
    int8_t current = 0;
    int8_t previous = 0;
    
    int8_t read();
    int readAxis(int axis);
};

thumbstick.cpp

#include "thumbstick.h"


// parameters for reading the joystick:
const int range = 128;    // output range of X or Y movement
const int threshold = 8;  // resting threshold

ThumbstickPosition::ThumbstickPosition() {
  
}

ThumbstickPosition::ThumbstickPosition(const int x, const int y, const int dx, const int dy) {
  this->x = x;
  this->y = y;
  this->dx = dx;
  this->dy = dy;
}

ThumbstickButton::ThumbstickButton(const int pin, const int xAxis, const int yAxis, PressButton btnPress, ReleaseButton btnRelease) {
  this->pin = pin; 
  this->xAxis = xAxis;
  this->yAxis = yAxis;
  this->btnPress = btnPress;
  this->btnRelease = btnRelease;
  pinMode(pin, INPUT_PULLUP);  // the switch pin
}

unsigned long ThumbstickButton::age() {
  return millis() - this->since;
}

int8_t ThumbstickButton::read() {
  int8_t r = digitalRead(this->pin);  // 1=up or 0=down.

  if (r == this->peek) {
    if (millis() - this->since > 40) {
      this->previous = this->current;
      this->current = r;
    }
  } else {
    // state changed
    
    this->peek = r;
    if (this->current == LOW && r == HIGH) {
      // button released
      const int ms = millis() - this->since;
      this->previous = this->current;
      this->current = r;
      this->pressed = false;
      this->btnRelease(ms);
    }
    
    this->since = millis();
  }
  
  return this->current;
}

bool ThumbstickButton::pushed() {
  if (this->read() == LOW) {
    uint32_t lastPress = millis();
    if (!this->pressed) {
      return this->pressed = this->btnPress();
    }
  }
  return false;
}

ThumbstickPosition ThumbstickButton::position() {
  this->positions[1] = this->positions[0];
  const int x = this->readAxis(xAxis);
  const int y = this->readAxis(yAxis);
  
  return this->positions[0] = ThumbstickPosition(x, y, x - this->positions[1].x, y - this->positions[1].y);
}

int ThumbstickButton::readAxis(int axis) {
  /*
  reads an axis (0 or 1 for x or y) and scales the analog input range to a range
  from 0 to <range>
 */

  // read the analog input:
  int reading = analogRead(axis);

  // map the reading from the analog input range to the output range:
  reading = map(reading, 0, 1023, 0, range);

  // if the output reading is outside from the rest position threshold, use it:
  int distance = reading - (range / 2);

  if (abs(distance) < threshold) {
    return 0;
  }

  // return the distance for this axis:
  return distance;

}

MPU 6050 Arduino Library

MPU 6050 Arduino Library

The MPU-6050 IMU (Inertial Measurement Unit) is a 3-axis accelerometer and 3-axis gyroscope sensor. The accelerometer measures the gravitational acceleration, and the gyroscope measures the rotational velocity. Additionally, this module also measures temperature.

Required Arduino IDE Library

gyro.h
#include "MPU6050.h"

class GyroPacket {

public:
  int16_t _ax, _ay, _az;
  int16_t _gx, _gy, _gz;
  int16_t _rx, _ry, _rz;

  double t,tx,tf,_pitch,_roll,_yaw;
  
  GyroPacket(int address, MPU6050 sensor);

  bool isMoving();

  // acceleration
  int16_t ax();
  int16_t ay();
  int16_t az();

  // gyro
  int16_t gx();
  int16_t gy();
  int16_t gz();

  // rotation
  int16_t rx();
  int16_t ry();
  int16_t rz();

  double pitch();
  double roll();
  double yaw();
  
private:
  int address;
  bool moving;

  //convert the accel data to pitch/roll
  void getAngle(int a, int b, int c);
  
};
gyro.cpp
#include <math.h>
#include <Wire.h>
#include "I2Cdev.h"
#include "MPU6050.h"
#include "gyro.h"


GyroPacket::GyroPacket(int address, MPU6050 sensor) {
  this->address = address;

  sensor.getMotion6(
    &_ax, &_ay, &_az,
    &_gx, &_gy, &_gz
  );

  sensor.getRotation(
    &_rx, &_ry, &_rz
  );

  getAngle(_ax, _ay, _az);
  
  this->t = ((sensor.getTemperature() - 1600) / 340) + 36.53;
  this->tf = (this->t * 9 / 5) + 32;

}

int16_t GyroPacket::ax() {
  return _ax;
}
int16_t GyroPacket::ay() {
  return _ay;
}
int16_t GyroPacket::az() {
  return _az;
}
int16_t GyroPacket::gx() {
  return _gx;
}
int16_t GyroPacket::gy() {
  return _gy;
}
int16_t GyroPacket::gz() {
  return _gz;
}
int16_t GyroPacket::rx() {
  return _rx;
}
int16_t GyroPacket::ry() {
  return _ry;
}
int16_t GyroPacket::rz() {
  return _rz;
}

//
////convert the accel data to pitch/roll
void GyroPacket::getAngle(int a, int b, int c) {
  double x = (double)a;
  double y = (double)b;
  double z = (double)c;
  double pitch = atan(x / sqrt((y * y) + (z * z)));
  double roll = atan(y / sqrt((x * x) + (z * z)));
  double yaw = atan(z / sqrt((x * x) + (y * y)));
  //convert radians into degrees
  this->_pitch = pitch * (180.0 / M_PI); //3.14159265359);
  this->_roll = roll * (180.0 / M_PI); //3.14159265359) ;
  this->_yaw = yaw * (180.0 / M_PI); //3.14159265359) ;

}

//convert the accel data to pitch/roll
double GyroPacket::pitch() {
  return _pitch;
// double pitch = atan((double)_ax / sqrt(((double)_ay * _ay) + (_az, _az)));
// return pitch * (180.0/M_PI);
}

double GyroPacket::roll() {
  return _roll;
// double roll = atan((double)_ay / sqrt(((double)_ax * _ax) + (_az * _az)));
// return roll * (180.0/M_PI);
}

double GyroPacket::yaw() {
  return _yaw;
// double yaw = atan((double)_az / sqrt(((double)_ax * _ax) + (_ay * _ay)));
// return yaw * (180.0/M_PI);
}

T-LOC Build

2023 April – North Site

STOP! March 5 2022 NO GO For Launch

NO GO – March NCR Launch

Mother Nature prevails. The weather forecast for the Briggsdale area has precipitation starting before midnight tonight and going until noon Saturday, turning to snow.  Sunday doesn’t look any better.  The predicted temps during launch time will be below freezing, albeit only a smidge with winds 17 to 20 mph blowing towards the road and with 100% cloud cover. Call me soft but we’re going to scrub.  SCORE in Pueblo plans to fly with warmer temps predicted but stronger winds. 

NOGO FOR LAUNCH – Dec 4, 2021

Members and Friends,
There is a scheduled Northern Colorado Rocketry launch event on Sat, 12/4 at the Atlas launch site.

Members and Friends,

Go figure. Opportunities change that quickly…..

National Forests along northern Front Range enter fire restrictions; provide fall safety reminders

Release Date: Nov 30, 2021

FORT COLLINS, Colo. (Nov. 30, 2021) – Due to dry and warm conditions and recent fire starts, the Arapaho and Roosevelt National Forests’ Clear Creek, Boulder and Canyon Lakes ranger districts along with the Pawnee National Grassland are enacting Stage 1 fire restrictions effective at 12:01 a.m. Wednesday, Dec. 1. Stage 1 fire restrictions limit where and what type of fires visitors can have and remain in place until rescinded.

The following is PROHIBITED under Stage 1 Fire Restrictions:

  • Igniting, building, maintaining, attending, or using a fire (including fires fueled by charcoal or briquettes) outside of a permanent metal or concrete fire pit or grate that the Forest Service has installed and maintained at its developed recreation sites (campgrounds and picnic areas).
  • Smoking, except in an enclosed vehicle or building, a developed recreation site, or while stopped in an area at least three feet in diameter that is barren or cleared of all flammable materials.
  • Operating a chainsaw without an effective and properly installed spark arrestor, a fire extinguisher kept with the operator, and a shovel.
  • Blasting, welding, or operating a torch with an open flame without being in a cleared area of at least 10 feet in diameter and having a fire extinguisher kept with the operator.
  • Using an explosive. This includes but is not limited to fuses, blasting caps, fireworks, rockets, exploding targets, tracers, and incendiary ammunition. (Fireworks are always prohibited on National Forest lands).
  •  

Violation of Stage 1 fire restrictions could result in a maximum fine of $5,000 for an individual or $10,000 for an organization, or imprisonment for more than six months, or both. If responsible for causing a wildfire, one could be held accountable for suppression costs of that fire.

Looks like SCORE is still planning to launch at the Hudson Ranch.

Northern Colorado Rocketry – Affiliated with both NAR and TRA

Oktoberfest 2021

North Site – Pawnee Grasslands
Sunday
October 3rd, 2021
October
55 Flights
30 Flyers
North Site – Pawnee Grasslands
Saturday
October 2nd, 2021
October
91 Flights
54 Flyers
North Site – Pawnee Grasslands
Friday
October 1st, 2021
October
67 Flights
23 Flyers

Photos

 

We are Good to Go with our scheduled and approved launch on Saturday April 5th and Sunday April 6th, from the North Site, subject to change – Mostly cloudy and High Winds predicted.  Range and waiver should be active by 9AM on Saturday.   The Pawnee National Grassland remains fragile and dry, so extra precautions are in order.  Please stay on the authorized roads and please don’t park more than 100’ off the road at the flight line.

 

 

 

Vehicles must remain on approved/maintained roadways.  Driving on the Pawnee Grasslands is not allowed during any NCR sanctioned activity.