# 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.

![UML-diagram](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LDLySMRFlYdWwoVZLq7%2Fuploads%2FVyS7PdSf4XBuERTWldQX%2Ffile.png?alt=media)

## PIRSensor

The PIRSensor class includes the code to get a value from the actual PIR sensor. The PIRSensor class exists&#x20;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.

```cpp
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.

```cpp
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.

```cpp
 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.

```cpp
 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.

```cpp
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.

```cpp
 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.

```cpp
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.

```cpp
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.

```cpp
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

```cpp
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.

```cpp
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.

```cpp
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.

```cpp
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.

```cpp
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.

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

### getMotion

This method returns a int motion.

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

### getHumidity

This method returns a double humidity.

```cpp
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

```cpp
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.

```cpp
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

```cpp
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:

```cpp
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.

```cpp
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.

```cpp
EnvironmentSensorBoard board(nullptr);
```

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

#### 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

##
