Firmware

In this chapter you will find information about the firmware that runs on our PCB's. You can find the program in the following Github repository: https://github.com/Projectwerk2-2018/Firmware

Prologue

We started from a project which already had LoRaWAN functionallity in it. The repository can be found here: https://github.com/janjongboom/mbed-os-example-lorawan-minimal

On the image below you see the UML-diagram of our firmware solution.

PIRSensor

The PIRSensor class includes the code to get a value from the actual PIR sensor. The PIRSensor class exists of 6 methods, they will be explained one by one.

PIRSensor (constructor)

This is the constructor of our PIRSensor class, here needed everything is initialized. As you can see, we make use of interrupts to detect is there is movement or not. First of all we make sure the interrupt makes use of a pulldown, this must be done otherwise the interrupt won't work as expected. Then we make two methods that wil be linked to an interrupt, one method wil be activated when there is an positive interrupt and the other one when there is an negative interrupt. then we initialize the variable "state" to zero.

PIRSensor::PIRSensor(PinName pin) : sensorInterrupt(pin) {
    sensorInterrupt.mode(PullDown); 
    sensorInterrupt.rise(callback(this, &PIRSensor::positive_edge_detected)); 
    sensorInterrupt.fall(callback(this, &PIRSensor::negative_edge_detected)); 
    this->state = LOW; 
}

start

This method is activated when the program starts. This method resets every variable and starts a timer, that we will use to get timestamps.

void PIRSensor::start(){ 
    lowtime = 0; 
    hightime = 0; 
    timer.reset(); 
    timer.start(); 
}

stop

This method is activated just before we want to send the data with one of the transceivers. Here the timer is stopped and with the if-statement the last "delta" is added to the right time, lowtime or hightime.

 void PIRSensor::stop(){ 
   timer.stop(); 
   if (state==LOW){
      int deltaT = get_delta(); 
      lowtime += deltaT; 
    }else{ 
      int deltaT = get_delta(); 
      hightime += deltaT; 
    } 
  }

positive_edge_detected

This method is part of the interrupts. If there is a positive interrupt, this method will be activated. In this method we put STATE to high and we add the "delta" to lowtime because that was the previous time period.

 void PIRSensor::positive_edge_detected() { 
   state = HIGH; 
   int deltaT = get_delta(); 
   lowtime += deltaT; 
 }

negative_edge_detected

This method does the same as the method "positive_edge_detected" but it works on the negative interrupt and add the delta to the hightime variable.

void PIRSensor::negative_edge_detected() { 
    state = LOW; 
    int deltaT = get_delta(); 
    hightime += deltaT; 
}

get_percentage_movement

This is to only getter from the PIRSensor class. this method returns the variable percentage, This percentage represents the high time in relation to the total time.

 int PIRSensor::get_percentage_movement(){ 
   int percentage = (1.0*hightime/(hightime+lowtime))*100; 
   return percentage; 
 }

get_delta

The code in this method appeared al lot in the code Before we had this method, so we made a private method get_delta and when we needed to read the time, we just have to use this method. This makes our code al lot more DRY.

int PIRSensor::get_delta(){ 
    int deltaT = timer.read_ms() - lowtime - hightime; 
    return deltaT; 
}

TemperatureHumidity + si7013

The TemperatureHumidity class includes the code to get a value from the actual temperature/ humidity sensor. Because the temperature and humidity sensor are in the same package we have chosen to make one class, namely TemperatureHumidity. the class si7013 is a library that we can use to get values from the sensors.

TemperatureHumidity (constructor)

This method is the constructor of this class, here we make use of the default constructor. The constructor makes it possible to use I2C.

TemperatureHumidity::TemperatureHumidity(I2C* sensorI2C) :rhtSensor(sensorI2C){ 
}

read_values

The code here is very similar to the one used to test the SI7013 alone (see Hardware Disign/Prototype/Testing temerature and humidity sensor/code operation)

check_availability

Check if the sensor is active and responding.

void TemperatureHumidity::read_values() { 
   if(!rhtSensor.check_availability(si7013, nullptr)){ 
       wait_ms(100);      
       //rhtSensor.measure
         //conversion
   else { 
     printf("Failed to check avail temperature/humidity\r\n"); 
   } 
 }

rhtSensor.measure

Perform measurement

int ret = rhtSensor.measure(si7013, nullptr); 
       wait_ms(100);

conversion

We call upon the get_humidity/ get_temperature methods to get the last measured relative humidity/ temperature data. Because the measured value is converted to mili-percent / mili-°C in the library the stored values need to be converted again to % / °C.

if (!ret) { 
         temperature = ((rhtSensor.get_temperature()/1000.0)); 
         humidity = ((rhtSensor.get_humidity()/1000)); 
       } else { 
         printf("Failed to read temperature/humidity\r\n"); 
       }

get_temperature

This method is a getters that returns the temperature variable. This method makes us of the private method "read_values", who reads tha values from the sensor.

int TemperatureHumidity::get_temperature(){ 
    read_values(); 
    return temperature; 
}

get_humidity

This method is a getters that returns the humidity variable. This method makes us of the private method "read_values", who reads tha values from the sensor.

int TemperatureHumidity::get_humidity(){ 
    read_values(); 
    return humidity; 
}

SensorData

SensorData (constructor)

As you can see, we made two constructors. The first constructor is the default constructor and set the three values to zero. The second constructor expects three arguments, these arguments will be initialized in the constructor.

SensorData::SensorData()
{ 
    temperature = 0.0; 
    motion = 0; 
    humidity = 0.0;
}

SensorData::SensorData(double inputTemperature, int iputMotion, double inputHumidity)
{ 
    temperature = inputTemperature; 
    motion = iputMotion; 
    humidity = inputHumidity;
}

getTemperature

This method returns a double temperature.

double SensorData::getTemperature(){ 
    return temperature;
}

getMotion

This method returns a int motion.

int SensorData::getMotion(){ 
    return motion;
}

getHumidity

This method returns a double humidity.

double SensorData::getHumidity(){ 
    return humidity;
}

EnvironmentBoard

The purpose of the EnvironmentSensorBoard is to send data to a transceiver. The idea is that you can chose a transceiver (LoRa- or terminaltransceiver) to which data will be sent.

This class has three methods.

Constructor

EnvironmentSensorBoard::EnvironmentSensorBoard(Transceiver * inputTransceiver)
: motionSensor(PA_9), sensorI2C(PC_1, PC_0) //PC_1=SDA, PC_0=SCL
{ 
    this->transceiver = inputTransceiver;
    temperatureSensor = new TemperatureHumidity(&sensorI2C);
}

update

Due to circumstances, we did not end up using this method. The function was put it into comment.

void EnvironmentSensorBoard::update()
{ 
    double temperature = temperatureSensor.get_temperature_value();
    int motion = motionSensor.get_percentage_movement();
    double humidity = humiditySensor.get_humidity_value();
    
    SensorData data(temperature, motion, humidity);
    
    transceiver->send_message(data);
}

get_data

SensorData EnvironmentSensorBoard::get_data ()
{
  double temperature = temperatureSensor->get_temperature();
  int motion = motionSensor.get_percentage_movement();
  double humidity = temperatureSensor->get_humidity();
  
  SensorData data (temperature, motion, humidity);
  
  return data;
}

SensorDataByteSerializer

The sensor data byte serializer puts the sensor data into a format that will be sent via LoRaWAN. You can see the format we've chosen here:

Sensor

Size

Datatype

Description

Temperature

2 bytes

int

Send an integer from the PCB

Humidity

1 byte

int

value between 0 and 100 (in %)

Movement

2 byte

int

Amount of movements in a certain timeframe

And here you can see the code we've made to realize this conversion:

void SensorDataByteSerializer::serialize(SensorData dataPacket, 
                                            uint8_t* payload, int maxPayload)
{
   if (maxPayload >= PAYLOAD_SIZE)
   {
       temp = dataPacket.getTemperature();
       hum = dataPacket.getHumidity();
       pir = dataPacket.getMotion();
       
       payload[0] = (uint8_t) temp >> 8;
       payload[1] = (uint8_t) temp & 0xFF;
       payload[2] = (uint8_t) hum;
       payload[3] = (uint8_t) pir >> 8;
       payload[4] = (uint8_t) pir & 0xFF;
   }
 }

Transceivers

As explained int the 'EnvironmentBoard' section, we want to chose between different transceivers, to which data will be sent. We have made several transceivers, which can be found bellow.

LoRa Transceiver

Due to circumstances, we did not make a separate LoRa transceiver class. In stead, all functionallity can be found in the main class.

main

The main function has a number of tasks:

  • Store the status of a call to LoRaWAN protocol,

  • Initialize LoRaWAN stack,

  • Prepare application callbacks,

  • Set number of retries in case of CONFIRMED messages,

  • Enable adaptive data rate,

  • Make your event queue dispatching events forever.

send_message

In the code below you will find the send_message function. The tx_buffer variable needs to be filled with 'serialized' data, which comes from the serializer class. This can be seen in lines 6 - 11.

static void send_message()
{
    uint16_t packet_len;
    int16_t retcode;
    
    SensorData data = board.get_data();
    SensorDataByteSerializer payload;
    packet_len = payload.payload_size();
    payload.serialize(data, tx_buffer, LORAMAC_PHY_MAXPAYLOAD);
    
    packet_len = 5;
    
    retcode = lorawan.send(MBED_CONF_LORA_APP_PORT, tx_buffer, packet_len,
                                MSG_CONFIRMED_FLAG);
                                
    if (retcode < 0){
        retcode == LORAWAN_STATUS_WOULD_BLOCK ? printf("send - WOULD BLOCK\r\n")
            : printf("\r\n send() - Error code %d \r\n", retcode);
        return;
    }
    
    printf("\r\n %d bytes scheduled for transmission \r\n", retcode);
    memset(tx_buffer, 0, LORAMAC_PHY_MAXPAYLOAD);
}

Note that a board was made in top of the program.

EnvironmentSensorBoard board(nullptr);

We pass nullptr as argument, this is because we did not end up moving the LoRaWAN functionallity to a separate class.

receive_message

The program has a receive_message function, but is not used.

lora_event_handler

This function has a lot of code, you can find it in our github repository (linked at the top of this page).

Fake Transceiver

Last updated