![](https://i.imgur.com/jrNK2Pn.png =250x65) # Compte rendu TP2 ``` NIANG Elhadji, SAMPIC Tom, MISKU Eigis ``` ## Objectives ### Question 1: For each sentence below, indicate if it is true or false. 1. LoRa has a long communication range, so it is likely that end-nodes deployed in the parcels will be able to communicate directly with the sink located in the town. **True** 2. In the worst case, the end-nodes will communicate with the sink with SF=7. **False, It will use SF12** 3. LoRa communications require low energy consumption, so the batteries of the end-nodes won’t have to be changed frequently. **True, this is what LowPowerWAN are about** ## Initial Setup ### Question 2 client.cpp ```cpp! #include <Arduino.h> #include <RH_RF95.h> #include <DHT.h> #include <RHDatagram.h> const uint8_t CLIENT_ADDRESS = 4; const uint8_t SERVER_ADDRESS = 3; RH_RF95 rf95; RHDatagram rhd(rf95, CLIENT_ADDRESS); void setup() { Serial.begin(9600); if (!rhd.init()) Serial.println("Init rhd failed"); rf95.setFrequency(868); // Set the frequency in MHz. rf95.setTxPower(0); // Set Tx power in dBm rf95.setModemConfig(RH_RF95::Bw125Cr45Sf128); // Set the modem configuration.sf7 bw 125 CR 4/5 Serial.println("Setup finished."); } void loop() { uint8_t data[] = "12345678"; // Add here the message to send uint8_t len = sizeof(data); rhd.sendto(data, len, SERVER_ADDRESS); rhd.waitPacketSent(); Serial.println("Message sent!"); Serial.println((char *)data); delay(2000); } ``` server.cpp ```cpp! #include <Arduino.h> #include <RH_RF95.h> #include <RHDatagram.h> const uint8_t CLIENT_ADDRESS = 4; const uint8_t SERVER_ADDRESS = 3; RH_RF95 rf95; RHDatagram rhd(rf95, SERVER_ADDRESS); void setup() { Serial.begin(9600); if (!rhd.init()) Serial.println("Init rhd failed"); rf95.setFrequency(868); // Set the frequency in MHz. rf95.setTxPower(13); // Set Tx power in dBm Serial.println("Setup finished."); } void loop() { uint8_t bufferReception[RH_MAX_MESSAGE_LEN]; uint8_t lenReception = sizeof(bufferReception); uint8_t senderAddress; if (rhd.waitAvailableTimeout(2000)) { if (rhd.recvfrom(bufferReception, &lenReception, &senderAddress)) { uint8_t data[] = "ack elhadji"; uint8_t len_ack = sizeof(data); blinkLedLong(); Serial.print("\n"); Serial.print("\n"); Serial.print("Message from the client "); Serial.print(senderAddress); Serial.print(": "); bufferReception[lenReception]=0; Serial.println((char *)bufferReception); delay(1000); } else { Serial.println("Reception fail"); } Serial.print("\n"); Serial.print("\n"); } else { Serial.println("No data received !!!"); // remplir le message qui semble approprié } } ``` Here we can visualize in the capture of the serial monitor that the message sent by the end-device which it's adress is 4 is well received by the sever ![](https://i.imgur.com/FH2jACK.png) ## Implementing a duty cycle > In Europe for instance, the ETSI regulation instance specifies that any device using the 868 MHz band is forbidden to send for more than 1% of the time, unless a listen-before talk mechanism is implemented. ### Question 3 ETSI: European Telecommunications Standards Institute ### Question 4, 5, 6 & 7 ```python! # Calculation functions def rawDataRate(sf, bw): return (sf * bw) / 2**sf def physicalDataRate(sf, bw, cr): return rawDataRate(sf, bw) * (4 /(4 + cr)) def symbolDuration(sf, bw): return 2**sf / bw preamble = 12.25 # symbols header = 8 # symbols app_header = 3 # bytes payload = 1 # bytes crc = 2 # bytes cr = 1 # 4/5 frame_duration = symbolDuration(7, 125e3) * (preamble + header) + (app_header + payload + crc) / physicalDataRate(7, 125e3, cr) f'one frame last {frame_duration:.2e} seconds' # > one frame last 2.18e-02 seconds # Let's take a 1 byte payload dc = 1 / 100 t_off = frame_duration / dc throughput = payload / ( t_off + frame_duration) f'useful transfer rate is {throughput:.2e} bytes/s' # > useful transfer rate is 4.53e-01 bytes/s # We know use a 20 bytes payload payload = 20 # bytes frame_duration = symbolDuration(7, 125e3) * (preamble + header) + (app_header + payload + crc) / physicalDataRate(7, 125e3, cr) f'one frame last {frame_duration:.2e} seconds' # > one frame last 2.53e-02 seconds dc = 1 / 100 t_off = frame_duration / dc throughput = payload / ( t_off + frame_duration) f'useful transfer rate is {throughput:.2e} bytes/s' # > useful transfer rate is 7.82e+00 bytes/s ``` ### Question 8 We demonstrated that aggregating packets into larger payloads is important because it reduces the ratio on control bytes among the total message length. Thus the transfer rate is better when taking only data bytes into account. Aggregating data in the code could be done the way we show below client.cpp ```cpp! #include <Arduino.h> #include <RH_RF95.h> #include <DHT.h> #include <RHDatagram.h> const uint8_t CLIENT_ADDRESS = 4; const uint8_t SERVER_ADDRESS = 3; const uint8_t MAX_PAYLOAD = 50; RH_RF95 rf95; RHDatagram rhd(rf95, CLIENT_ADDRESS); uint8_t *buf = (uint8_t *)malloc(MAX_PAYLOAD + 1); int counter = 0; void setup() { buf[MAX_PAYLOAD] = 0; Serial.begin(9600); if (!rhd.init()) Serial.println("Init rhd failed"); rf95.setFrequency(868); // Set the frequency in MHz. rf95.setTxPower(0); // Set Tx power in dBm rf95.setModemConfig(RH_RF95::Bw125Cr45Sf128); // Set the modem configuration.sf7 bw 125 CR 4/5 Serial.println("Setup finished."); } void loop() { // Allocate a buffer of size MAX_PAYLOAD // generate 8 bits of random data and put it in the buffer at counter position buf[counter] = random(0, 25) + 'a'; // if the buffer is full, send it to the server if (counter == MAX_PAYLOAD - 1) { Serial.println("Sending data to server"); Serial.println((char *)buf); rhd.sendto(buf, MAX_PAYLOAD, SERVER_ADDRESS); rhd.waitPacketSent(); Serial.println("Data sent"); rf95.sleep(); counter = 0; } else { counter++; } delay(100); } ``` ### Question 12 We empirically tried to use the `sleep()` function. However in our test it appears that we got strange undefined behavior. We removed the `sleep()` part since it would have required further investigations that we did not have time to do. ## Implementing acknowledgments ### Question 13 For the confirmation of the reception of the message that is commonly called ack mechnism, for the end device we can decide either to send and wait the ack message from the server or repeating the same message multiple times to ensure that the server will at leat receive on among them. For that, a choice must be made. By comparing both of the mechanisms, it is clear that the latter can be energy consuming beacause for the first, the end-device, after sending the message will wait for the ack from the server. In the server, we have implemented the ack mechanism that will allows to send a reception confirmation to the end-device as soon as it's message is received. server.cpp ```cpp! #include <Arduino.h> #include <RH_RF95.h> #include <RHDatagram.h> const uint8_t CLIENT_ADDRESS = 4; const uint8_t SERVER_ADDRESS = 3; RH_RF95 rf95; RHDatagram rhd(rf95, SERVER_ADDRESS); void setup() { Serial.begin(9600); if (!rhd.init()) Serial.println("Init rhd failed"); rf95.setFrequency(868); // Set the frequency in MHz. rf95.setTxPower(13); // Set Tx power in dBm Serial.println("Setup finished."); } void loop() { uint8_t bufferReception[RH_MAX_MESSAGE_LEN]; uint8_t lenReception = sizeof(bufferReception); uint8_t senderAddress; if (rhd.waitAvailableTimeout(2000)) { if (rhd.recvfrom(bufferReception, &lenReception, &senderAddress)) { uint8_t data[] = "ack elhadji"; uint8_t len_ack = sizeof(data); blinkLedLong(); Serial.print("\n"); Serial.print("\n"); Serial.print("Message from the client "); Serial.print(senderAddress); Serial.print(": "); bufferReception[lenReception]=0; Serial.println((char *)bufferReception); delay(1000); rhd.sendto((char*)data, len_ack, senderAddress); rhd.waitPacketSent(); Serial.print("--------------------------ACK sent to Client "); Serial.print(senderAddress); Serial.print(" --------------------------------\n"); } else { Serial.println("Reception fail"); } Serial.print("\n"); Serial.print("\n"); } else { Serial.println("No data received !!!"); // remplir le message qui semble approprié } } ``` ### Question 14 The ack message may contain a flag that will allow the end-device to know that the message is received. In our case, we have just specified a text message "ack" to inform the end-device that the message is received by the server. ### Question 15 If the ACK is sent and the end-device did not receive it, the latter, after waiting for a delay can decide to retransmit the message because it consider that the sink lost them. And in the sink we can have duplicated messages. ### Question 16 Limited by many constraints such as duty cycle, the large number of end-devices it serves, number of messages sent by an end-device per time the sink will not be able to send an AKC for a frame, it is quitely impossible. ### Question 17 If the sink can not send an ACK for a frame, the end-device can consider this lost and can also try to send the message again. ### Here below client code with ACK implementation : client.cpp ```cpp! #include <Arduino.h> #include <RH_RF95.h> #include <DHT.h> #include <RHDatagram.h> const uint8_t CLIENT_ADDRESS = 4; const uint8_t SERVER_ADDRESS = 3; const uint8_t MAX_PAYLOAD = 50; RH_RF95 rf95; RHDatagram rhd(rf95, CLIENT_ADDRESS); uint8_t buf[MAX_PAYLOAD + 1]; uint8_t resend_buf[MAX_PAYLOAD + 1]; // ack timeout in ms const uint16_t ACK_TIMEOUT = 3000; // timer used for ack timeout unsigned long start; // ack received flag bool ack = true; int counter = 0; void setup() { buf[MAX_PAYLOAD] = 0; resend_buf[MAX_PAYLOAD] = 0; Serial.begin(9600); if (!rhd.init()) Serial.println("Init rhd failed"); rf95.setFrequency(868); // Set the frequency in MHz. rf95.setTxPower(0); // Set Tx power in dBm rf95.setModemConfig(RH_RF95::Bw125Cr45Sf128); // Set the modem configuration.sf7 bw 125 CR 4/5 Serial.println("Setup finished."); // fill buf with random bytes of data for (int i = 0; i < MAX_PAYLOAD; i++) { buf[i] = random(0, 25) + 'a'; counter++; } } void loop() { // Allocate a buffer of size MAX_PAYLOAD // generate 8 bits of random data and put it in the buffer at counter position buf[counter] = random(0, 25) + 'a'; // if the buffer is full, send it to the server if (counter >= MAX_PAYLOAD - 1) { Serial.println("Sending data to server because buffer is full"); Serial.println((char *)buf); // new payload override the old one if ack has not been received at this point rhd.sendto(buf, MAX_PAYLOAD, SERVER_ADDRESS); rhd.waitPacketSent(); Serial.println("Data sent"); ack = false; start = millis(); // copy buf into resend_buf memcpy(resend_buf, buf, MAX_PAYLOAD); // rf95.sleep(); counter = 0; } else { counter++; // every 10th iteration if ack is false, try to recv an ack // check if ack is not received after timeout if (!ack && millis() - start < ACK_TIMEOUT) { // reset reception buffer // memset(rx_buf, 0, RH_RF95_MAX_MESSAGE_LEN + 1); // wait for a message if (rf95.available()) { // allocate a reception buffer of max lora size uint8_t rx_buf[MAX_PAYLOAD]; // rx_buf length uint8_t rx_len; uint8_t sender; if (rhd.recvfrom(rx_buf, &rx_len, &sender)) { // check if message is ACK if (rx_buf[0] == 'a' && rx_buf[1] == 'c' && rx_buf[2] == 'k') { Serial.println("Ack received"); Serial.println("size of buf"); Serial.println(rx_len); Serial.println((char *)rx_buf); // print the sender address Serial.print("Sender: "); Serial.println(sender); ack = true; rf95.sleep(); } else { Serial.println("Message received but not an ack"); // print message Serial.println("size of buf"); Serial.println(rx_len); Serial.println((char *)rx_buf); // print the sender address Serial.print("Sender: "); Serial.println(sender); } } // } else { // rf95.sleep(); } } } // rf95.sleep(); } if (!ack && millis() - start > ACK_TIMEOUT) { Serial.println("Ack not received"); Serial.println("Resending data"); rhd.sendto(resend_buf, MAX_PAYLOAD, SERVER_ADDRESS); rhd.waitPacketSent(); Serial.println("Data sent"); start = millis(); // rf95.sleep(); } delay(500); } ```