Instalar en Raspberry Pi OS 64 bits:
Install using the apt repository
Grupo de Investigación de Redes de iNformación Inalámbricas
Instalar en Raspberry Pi OS 64 bits:
Install using the apt repository
Referencia: https://www.home-assistant.io/installation/raspberrypi-other
Instalar:
docker run -d \ --name homeassistant \ --privileged \ --restart=unless-stopped \ -e TZ=MY_TIME_ZONE \ -v /PATH_TO_YOUR_CONFIG:/config \ -v /run/dbus:/run/dbus:ro \ --network=host \ ghcr.io/home-assistant/home-assistant:stable
Actualizar:
# f this returns "Image is up to date" then you can stop here docker pull ghcr.io/home-assistant/home-assistant:stable # stop the running container docker stop homeassistant # remove it from Docker's list of containers docker rm homeassistant # finally, start a new one docker run -d \ --name homeassistant \ --restart=unless-stopped \ --privileged \ -e TZ=MY_TIME_ZONE \ -v /PATH_TO_YOUR_CONFIG:/config \ -v /run/dbus:/run/dbus:ro \ --network=host \ ghcr.io/home-assistant/home-assistant:stable
Pluviometro LoRaWan con open hardware y open software
Las instrucciones para control del pluviómetro y sensores complementarios se encuentran segmentadas por bloques:
[ sensores ] [ envía ] [ recibe ] [ principal ]
...
El bloque de sensores se simplifica por partes de control y comunicación acorde al tipo de sensor:
El pluviómetro se centra en medir el vaciado de la "doble cubeta basculante".
Para la detección se usa un interruptor magnético que marca el cambio de estado mediante un pulso (pulse) conectado al pin asignado (pluvioPin). Para que la detección sea independiente del estado del dispositivo se usa una interrupción de instrucciones (pulse_tip) que cuenta el pulso (pulse) en flanco de subida (RISING)y marca el evento (pulse_flag).
attachInterrupt(pluvioPin, pulse_tip, RISING);
El dispositivo cuenta los pulsos generados durante un minuto (sample_period) y los almacena en una lista (pulse_list) hasta la transmisión de los datos hacia el gateway LoRa.
El sensor de estado de batería es un ADC que toma una lectura del estado de la batería antes de cada transmisión LoRa.
Los sensores de humedad, temperatura y presión son de tipo I2C que se activan solo al ser leídos en cada ciclo de transmisión mediante el pin Vext, Posterior a la lectura apagan para optimizar el ahorro de energía.
Antes de cada lectura, se realiza una verificación de estado de conexión al sensor optimizando el tiempo de éste proceso en caso de fallas del sensor expuesto a condiciones ambientales.
// sensors block // pluviometer pulse counter at interrupt void pulse_tip(){ if (pulse<=255){pulse = pulse + 1;} pulse_flag = true; } void pulse_check(){ if (pulse_flag==true){ if (pulse>255){pulse=255;} pulse_flag = false; tipfree = 0; if (serial_msg){ Serial.print("_|_: ");Serial.println(pulse); } turnOnRGB(128,200);turnOffRGB(); } } void minute_check(){ int i = 0; pulse_list[i_minute] = pulse; if (i_minute>=n_minute){ // pluviometer update for (i = 0; i < n_minute; i = i + 1) { pluviometer[i] = pulse_list[i]; pulse_list[i] = 0; } i_minute = 0; loop_0 = false; sensor_flag = true; tx_done = false; } if (serial_msg){ Serial.print("i_minute:");Serial.print(i_minute); Serial.print(" ; pulse_list:"); for (i = 0; i < n_minute; i = i + 1) { Serial.print(" "); Serial.print(pulse_list[i]); } Serial.print(" ; pluviometer:"); for (i = 0; i < n_minute; i = i + 1) { Serial.print(" "); Serial.print(pluviometer[i]); } Serial.print(" ; tipfree "); Serial.println(tipfree); } } void read_sensors(){ batteryVoltage = getBatteryVoltage(); // sensors check i2c on byte sensor_error, address; digitalWrite(Vext, LOW); delay(300); Wire.begin(); humitemp_active = false; Wire.beginTransmission(0x40); sensor_error = Wire.endTransmission(); if (sensor_error == 0){humitemp_active = true;} prestemp_active = false; Wire.beginTransmission(0x77); sensor_error = Wire.endTransmission(); if (sensor_error == 0){prestemp_active = true;} if (humitemp_active){hdc1080.begin(0x40);} if (prestemp_active){bmp.begin();} if (humitemp_active){ ht_temp = hdc1080.readTemperature(); ht_humi = hdc1080.readHumidity(); } float bar_alti = 0, bar_sea_pres = 0, bar_sea_alt = 0; if (prestemp_active){ bar_temp = bmp.readTemperature(); bar_pres = bmp.readPressure(); bar_alti = bmp.readAltitude(); bar_sea_pres = bmp.readSealevelPressure(); bar_sea_alt = bmp.readAltitude(100800); } // sensors I2C off Wire.end(); digitalWrite(Vext, HIGH); if (serial_msg){ Serial.print("Battery = ");Serial.print(batteryVoltage); Serial.print(" ; --- sensor "); Serial.print("hum_temp: "); Serial.print(humitemp_active); Serial.print(" ; bar_temp: "); Serial.println(prestemp_active); if (humitemp_active){ Serial.print("HDC1080 Temperature = "); Serial.print(ht_temp); Serial.print(" C ; Humidity = "); Serial.print(ht_humi);Serial.println(" %"); } if (prestemp_active){ Serial.print("BMP180 Temperature = "); Serial.print(bar_temp);Serial.println(" C"); Serial.print(" Pressure = "); Serial.print(bar_pres);Serial.print(" Pa ; Altitude = "); Serial.print(bar_alti);Serial.println(" meters"); Serial.print(" SeaLevel(calc)= "); Serial.print(bar_sea_pres); Serial.print(" Pa ; Altitude = "); Serial.print(bar_sea_alt); Serial.println(" meters"); } Serial.println(""); } } void onSleep(){ Serial.print("\n i_minute:");Serial.print(i_minute); Serial.printf(" ;Going into lowpower mode, %d ms later wake up.\r\n",timetillwakeup); lowpower = 1; //timetillwakeup ms later wake up; timetillwakeup = sample_period*1000 - (millis()-sample_ti); TimerSetValue( &wakeUp, timetillwakeup ); TimerStart( &wakeUp ); } void onWakeUp(){ Serial.println(" ... Woke up by time");//, %d ms later into lowpower mode.\r\n",timetillsleep); lowpower = 0; sleep_flag = false; sleep_done = true; //timetillsleep ms later into lowpower mode; //TimerSetValue( &sleep, timetillsleep ); //TimerStart( &sleep ); }
Adicionalmente existen dos procedimientos usados para el control del modo de ahorro de energía: onSleep() y onWakeup(), que junto a la instrucción lowPowerHandler() permiten un mayor ahorro de energía en los ciclos donde no se han producido pulsos del pluviómetro. Esto minimiza el uso de batería ante los periodos sin lluvia y manteniendo aún el registro por minuto durante largos periodos.
Básicamente, se usa el modo de ahorro de energía si no han ocurrido eventos de conteo del pluviómetro durante al menos un ciclo de n_minute, caso contrario se mantiene el controlador activo.
[ sensores ] [ envía ] [ recibe ] [ principal ]
...
La trama se configura usando los parámetros obtenidos en el dispositivo para cada sensor.
/* Prepares the payload of the frame */ static void prepareTxFrame( uint8_t port ) { unsigned char *puc; signed char *pucs; // trama appDataSize = 15 + n_minute; // 15+pluvio_list size appData[0] = ack_rssi; //Ack leido en dispositivo appData[1] = ack_snr; appData[2] = ack_datarate; appData[3] = (uint8_t)batteryVoltage; appData[4] = (uint8_t)(batteryVoltage>>8); // convierte float a bytes int ht_temp_int = round(ht_temp*100); pucs = (signed char *)(&ht_temp_int); appData[5] = pucs[0]; appData[6] = pucs[1]; // convierte float a bytes int ht_humi_int = round(ht_humi*100); puc = (unsigned char *)(&ht_humi_int); appData[7] = puc[0]; appData[8] = puc[1]; // convierte float a bytes int bar_temp_int = round(bar_temp*100); pucs = (signed char *)(&bar_temp_int); appData[9] = pucs[0]; appData[10] = pucs[1]; // convierte float a bytes puc = (unsigned char *)(&bar_pres); appData[11] = puc[0]; appData[12] = puc[1]; appData[13] = puc[2]; appData[14] = puc[3]; for (int i = 0; i < n_minute; i = i + 1) { appData[15+i] = pluviometer[i]; } // if (serial_msg){ // Serial.print("send pluviometer:"); // for (int i = 0; i < n_minute; i = i + 1) { // Serial.print(" "); // Serial.print(pluviometer[i]); // } // Serial.println(""); // } for (int i = 0; i < n_minute; i = i + 1) { pluviometer[i]=0; } }
[ sensores ] [ envía ] [ recibe ] [ principal ]
...
Manejo de tramas recibidas por el dispositivo para control o recibo de recibidos (Ack)
//downlink data handle and downLink Ack Handle functions void downLinkDataHandle(McpsIndication_t *mcpsIndication) { // revisa parametros Serial.print("\nLlegó un mensaje para dispositivo..."); // Serial.print("Rssi: "); // Serial.println(mcpsIndication->Rssi); // Serial.printf("+REV DATA:%s,RXSIZE %d,PORT %d\r\n", // mcpsIndication->RxSlot ? "RXWIN2" : "RXWIN1", // mcpsIndication->BufferSize, mcpsIndication->Port); // Serial.print("+REV DATA:"); // for (uint8_t i = 0; i < mcpsIndication->BufferSize; i++) { // Serial.printf("%02X", mcpsIndication->Buffer[i]); // } // parametros de recepcion ack_rssi = uint8_t(abs(mcpsIndication->Rssi)); ack_snr = uint8_t(mcpsIndication->Snr); ack_datarate = uint8_t(mcpsIndication->RxDoneDatarate); // recibido de trama Up_rssi = uint8_t(mcpsIndication->Buffer[0]); // Serial.print("Rx ack_Rssi:-"); Serial.print(ack_rssi); Serial.print(", ack_Snr:");Serial.print(ack_snr); Serial.print(", ack_Datarate: ");Serial.println(ack_datarate); Serial.print(" Up_rssi:"); Serial.print(-1*Up_rssi); Serial.printf(" +REV DATA:%s,RXSIZE %d,PORT %d\r",mcpsIndication->RxSlot?"RXWIN2":"RXWIN1",mcpsIndication->BufferSize,mcpsIndication->Port); Serial.println(); } void downLinkAckHandle(McpsIndication_t *mcpsIndication){ ack_rssi = uint8_t(abs(mcpsIndication->Rssi)); ack_snr = uint8_t(mcpsIndication->Snr); ack_datarate = uint8_t(mcpsIndication->RxDoneDatarate); tx_done = true; // if (serial_msg){ // Serial.println(' '); // Serial.print(" ack received(rssi,snr,datarate): -"); // Serial.print(ack_rssi);Serial.print(" ,"); // Serial.print(ack_snr);Serial.print(" ,"); // Serial.println(ack_datarate); // } }
[ sensores ] [ envía ] [ recibe ] [ principal ]
...
El bloque principal se basa en el esquema básico de LoRaWan usado para sensores, cambiando la parte de ahorro de energía entre transmisiones DEVICE_STATE_SLEEP a una controlada por periodos de muestreo del pluviómetro con los procedimientos onSleep() y onWakeUp() descritos en la parte de sensores. Se controla las actividades de Lectura de sensores y modos de ahorro de energía por la sucesión de cada evento.
// LoRaWan Pluviometer, temperature, humidity and barometric pressure // 2023 April // http://blog.espol.edu.ec/girni/lorawan-pluviometro-ino/ #include "LoRaWan_APP.h" #include "Arduino.h" #include <Wire.h> #include <HDC1080.h> #include <BMP180.h> /* set LoraWan_RGB to Active,the RGB active in loraWan * red |sending; purple | joined done; * blue |RxWindow1; yellow | means RxWindow2; * green | received done; */ /* LoRaWan: OTAA parameters*/ uint8_t devEui[] = { 0x2e, 0x4f, 0xa4, 0xdd, 0xf0, 0x2f, 0x06, 0xeb }; uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t appKey[] = { 0x13, 0xb9, 0xd1, 0x66, 0x30, 0x2a, 0xeb, 0x53, 0x46, 0x6c, 0x0d, 0x2d, 0xa2, 0x31, 0x6b, 0xf0 }; /* ABP parameters*/ uint8_t nwkSKey[] = { 0xe2, 0x28, 0x89, 0xe0, 0x73, 0x22, 0xcb, 0xd1, 0xa7, 0x95, 0x64, 0x2e, 0xdb, 0xe5, 0x94, 0x42 }; uint8_t appSKey[] = { 0x1a, 0xfc, 0x10, 0xc5, 0x6f, 0xb8, 0xba, 0x86, 0x0d, 0xf3, 0xcf, 0xc5, 0xd2, 0xdb, 0x44 ,0xb8 }; uint32_t devAddr = ( uint32_t )0x01d06174; /*LoraWan channelsmask, default channels 0-7*/ uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 }; /*Select in arduino IDE tools*/ LoRaMacRegion_t loraWanRegion = ACTIVE_REGION; DeviceClass_t loraWanClass = LORAWAN_CLASS; bool overTheAirActivation = LORAWAN_NETMODE; bool loraWanAdr = LORAWAN_ADR; bool keepNet = LORAWAN_NET_RESERVE; bool isTxConfirmed = LORAWAN_UPLINKMODE; // sample period uint8_t sample_min = 0; uint8_t sample_seg = 5; uint32_t sample_period = (sample_min*60 + sample_seg); uint32_t appTxDutyCycle = (sample_min*60 + sample_seg)*1000; // min*seg*ms uint8_t appPort = 4; /* Application port */ /* trials to transmit frame, if didn't receive ack. * The MAC performs a datarate adaptation, * Tx nb|Data Rate * -----|---------- * 1 |DR * 5 | max(DR-2,0) * 2 |DR * 6 | max(DR-2,0) * 3 |max(DR-1,0) * 7 | max(DR-3,0) * 4 |max(DR-1,0) * 8 | max(DR-3,0) */ uint8_t confirmedNbTrials = 4; // Ack reception parameters uint8_t ack_rssi = 0; uint8_t ack_snr = 0; uint8_t ack_datarate = 0; uint8_t Up_rssi = 0; // serial print messages bool serial_msg = true; // time variables and control check unsigned long sample_t0 = 0; unsigned long sample_ti = 0; unsigned long sample_dt = 0; bool pluvio_flag = false; bool sensor_flag = false; unsigned long tx_t0 = 0; bool tx_flag = false; bool tx_done = false; uint8_t tipfree = 0; bool loop_0 = true; bool sleep_flag = false; bool sleep_done = true; uint8_t i_minute = 0; const uint8_t n_minute = 5; uint8_t pulse_list[n_minute]; uint8_t pluviometer[n_minute]; uint8_t pulse = 0; bool pulse_flag = false; const uint8_t pluvioPin = GPIO5; #define timetillsleep 60*60*1000 int timetillwakeup = sample_period*1000; static TimerEvent_t sleep; static TimerEvent_t wakeUp; uint8_t lowpower=1; //sensors libraries HDC1080 hdc1080; // humidity and temperature sensor BMP085 bmp; // barometer and temperature sensor // sensors I2C active check and value variables bool humitemp_active = false; bool prestemp_active = false; float ht_temp = 0, ht_humi = 0; float bar_temp = 0, bar_pres = 0; uint16_t batteryVoltage = 0; void setup() { Serial.begin(115200); #if(AT_SUPPORT) enableAt(); #endif // OLED display status //LoRaWAN.displayMcuInit(); pinMode(pluvioPin, INPUT); //OUTPUT_PULLUP); attachInterrupt(pluvioPin, pulse_tip, RISING); for (int i = 0; i < n_minute; i = i + 1) { pulse_list[i] = 0; pluviometer[i] = 0; } // sensors I2C Vcc pin control pinMode(Vext, OUTPUT); read_sensors(); // time variables iniciated sample_t0 = millis(); sample_ti = sample_t0; tx_t0 = sample_t0; deviceState = DEVICE_STATE_INIT; LoRaWAN.ifskipjoin(); //if joinned,skip // lowpower mode Radio.Sleep( ); TimerInit( &sleep, onSleep ); TimerInit( &wakeUp, onWakeUp ); onWakeUp(); } void loop() { if(lowpower){lowPowerHandler();} pulse_check(); sample_t0 = millis(); sample_dt = abs(sample_t0-sample_ti); if (sample_dt>=sample_period*1000){ sample_ti = sample_t0; i_minute = i_minute + 1; if (pulse==0){ // tipfree counter do not overflow if (tipfree<=250){tipfree = tipfree + 1;} if (tipfree>250){tipfree = 1;} } pulse = 0; minute_check(); } // read I2C sensors at i_minute=0 if (i_minute==0 && sensor_flag == true){ Serial.println(" reading sensors ..."); read_sensors(); sensor_flag = false; } // tx check if (i_minute==0 && tx_flag==false && tx_done == false && loop_0==false){ tx_t0 = sample_t0; deviceState = DEVICE_STATE_SEND; tx_flag = true; tx_done = false; } if (i_minute >= n_minute){tx_flag = false;} // sleep check if (i_minute==0 && tipfree>=n_minute && sleep_flag==false && sleep_done==true && tx_done==true){ sleep_flag = true; sleep_done = false; lowpower = 1; //timetillsleep ms later into lowpower mode; TimerSetValue( &sleep, 1 ); TimerStart( &sleep ); } if (i_minute > 0 && tipfree>=n_minute && sleep_flag==false && sleep_done==true){ sleep_flag = true; sleep_done = false; lowpower = 1; //timetillsleep ms later into lowpower mode; TimerSetValue( &sleep, 1 ); TimerStart( &sleep ); } switch( deviceState ) { case DEVICE_STATE_INIT: { #if(LORAWAN_DEVEUI_AUTO) LoRaWAN.generateDeveuiByChipID(); #endif #if(AT_SUPPORT) getDevParam(); #endif printDevParam(); LoRaWAN.init(loraWanClass,loraWanRegion); deviceState = DEVICE_STATE_JOIN; break; } case DEVICE_STATE_JOIN: { //LoRaWAN.displayJoining(); LoRaWAN.join(); break; } case DEVICE_STATE_SEND: { //LoRaWAN.displaySending(); prepareTxFrame( appPort ); LoRaWAN.send(); deviceState = DEVICE_STATE_CYCLE; break; } case DEVICE_STATE_CYCLE: { // Schedule next packet transmission //txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND ); //LoRaWAN.cycle(txDutyCycleTime); deviceState = DEVICE_STATE_SLEEP; break; } case DEVICE_STATE_SLEEP: { //LoRaWAN.displayAck(); if (sleep_flag==true && sleep_done==false){ // if (serial_msg){ // Serial.print(" --- Sleep mode ; deviceState : "); // Serial.print(deviceState); // Serial.print(" ; i_minute : "); Serial.println(i_minute); // } //LoRaWAN.sleep(); sleep_done = true; } break; } default: { deviceState = DEVICE_STATE_INIT; break; } } }
function Decode(fPort, bytes, variables) {
var Down_rssi = -1*parseInt(bytes[0]);
var Down_snr = bytes[1];
var Down_datarate = bytes[2];
// usando entero
var unalectura = (bytes[4] << 8) |(bytes[3]);
unalectura = (unalectura/1000);
unalectura = +unalectura.toFixed(2);
var appData = {'Down_rssi':Down_rssi,'Down_snr':Down_snr,
'Down_datarate':Down_datarate,
'bateria_V': unalectura}
return appData;
}
function Encode(fPort, obj, variables) {
var UP_rssi = obj["UP_rssi"];
// var Up_snr = obj["UP_snr"];
var mensaje = [UP_rssi];
return mensaje;
}
mqtt:
sensor:
- name: "rssi_up_cc50"
unique_id: cc27rssiup
state_topic: "application/1/device/a53ec615aede3f50/event/up"
unit_of_measurement: "dBm"
value_template: "{{ value_json.rxInfo[0].rssi}}"
#availability:
# - topic: "home/sensor1/status"
payload_available: "online"
payload_not_available: "offline"
json_attributes_topic: "application/1/device/a53ec615aede3f50/event/up"
Desencadenante
platform: mqtt topic: application/1/device/a53ec615aede3f50/event/up
Accion
service: mqtt.publish
data:
qos: 0
retain: false
topic: application/1/device/a53ec615aede3f50/command/down
payload: '{"confirmed":false,"fPort":4,"object":{"UP_rssi":{{(states("sensor.rssi_up_cc50") | int)*(-1)}}}}'
Probador de Rssi de subida y bajada
Hardware: Heltec cubecell AB02, con pantalla OLED incorporada.
void displayconectando(){ display2.setFont(ArialMT_Plain_16); display2.setTextAlignment(TEXT_ALIGN_CENTER); display2.clear(); display2.drawString(58, 22, "Joining..."); display2.display(); } void displayConectado(){ display2.clear(); display2.drawString(64, 22, "Conectado_OK"); display2.display(); delay(500); } void displayTransmitiendo(){ digitalWrite(Vext,LOW); display2.init(); display2.setFont(ArialMT_Plain_16); display2.setTextAlignment(TEXT_ALIGN_CENTER); display2.clear(); display2.drawString(58, 22, "Tx..."); display2.display(); delay(500); } void displayPaqRecibido(){ char temp[25]; display2.clear(); display2.setFont(ArialMT_Plain_16); display2.setTextAlignment(TEXT_ALIGN_LEFT); sprintf(temp,"Rx snr:%d,dr:%d",Down_snr, Down_datarate); display2.drawString(0, 0, temp); //22 sprintf(temp,"rssiDw: -%d",Down_rssi); display2.setFont(ArialMT_Plain_24); display2.setTextAlignment(TEXT_ALIGN_RIGHT); display2.drawString(128, 16, temp); sprintf(temp,"rssiUp: -%d",Up_rssi); display2.setFont(ArialMT_Plain_24); display2.setTextAlignment(TEXT_ALIGN_RIGHT); display2.drawString(128, 40, temp); display2.drawString(0,0, temp); if(loraWanClass==CLASS_A) { display2.setFont(ArialMT_Plain_10); display2.setTextAlignment(TEXT_ALIGN_LEFT); display2.drawString(28, 50, "Into deep sleep in 2S"); } display2.display(); delay(10000); display2.clear(); if(loraWanClass==CLASS_A){ digitalWrite(Vext,HIGH); display2.stop(); } } void displayAckRecibido(){ char temp[25]; display2.clear(); display2.setFont(ArialMT_Plain_16); display2.setTextAlignment(TEXT_ALIGN_LEFT); sprintf(temp,"ACK snr:%d,dr:%d",confirmaSnr, confirmaDatarate); display2.drawString(0, 0, temp); //22 sprintf(temp,"rssiDw: -%d",confirmaRssi); display2.setFont(ArialMT_Plain_24); display2.setTextAlignment(TEXT_ALIGN_RIGHT); display2.drawString(128, 16, temp); if(loraWanClass==CLASS_A) { display2.setFont(ArialMT_Plain_10); display2.setTextAlignment(TEXT_ALIGN_LEFT); display2.drawString(28, 50, "Into deep sleep in 2S"); } display2.display(); delay(10000); if(loraWanClass==CLASS_A) { digitalWrite(Vext,HIGH); display2.stop(); } }
// Lectura de Rssi Snr, datarate Up/Downlink // Datos Downlink de la trama de confirmación anterior // http://blog.espol.edu.ec/girni/lorawan-enlaces-up-down-archivo-ino/ #include "LoRaWan_APP.h" #include "Arduino.h" #include <Wire.h> #include "HT_SH1107Wire.h" SH1107Wire display2(0x3c, 500000, SDA, SCL ,GEOMETRY_128_64,GPIO10); // addr, freq, sda, scl, resolution, rst /* set LoraWan_RGB to Active,the RGB active in loraWan * red |sending; purple | joined done; * blue |RxWindow1; yellow | means RxWindow2; * green | received done; */ /* Conexión LoRa: OTAA parametros*/ uint8_t devEui[] = { 0xa5, 0x3e, 0xc6, 0x15, 0xae, 0xde, 0x3f, 0x50 }; uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t appKey[] = { 0x88, 0xbe, 0x25, 0xca, 0x2c, 0xcf, 0x31, 0x85, 0x51, 0x2d, 0xee, 0xe2, 0x80, 0x31, 0x8e, 0x01 }; /* ABP parametros*/ uint8_t nwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 }; uint8_t appSKey[] = { 0x47, 0xdc, 0xac, 0x5f, 0xc2, 0x32, 0x24, 0x31, 0xdf, 0xf1, 0xff, 0xf9, 0x46, 0xe5, 0x2e, 0x17 }; uint32_t devAddr = ( uint32_t )0x007bc4150; /*LoraWan channelsmask, default channels 0-7*/ uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 }; /*Select in arduino IDE tools*/ LoRaMacRegion_t loraWanRegion = ACTIVE_REGION; DeviceClass_t loraWanClass = LORAWAN_CLASS; bool overTheAirActivation = LORAWAN_NETMODE; bool loraWanAdr = LORAWAN_ADR; bool keepNet = LORAWAN_NET_RESERVE; bool isTxConfirmed = LORAWAN_UPLINKMODE; uint8_t duermemin = 0; //15 uint8_t duermeseg = 300; //0 uint32_t appTxDutyCycle = (duermemin*60 + duermeseg)*1000; // min*seg*ms uint8_t appPort = 4; /* Application port */ /* trials to transmit frame, if didn't receive ack. * The MAC performs a datarate adaptation, * Tx nb|Data Rate * -----|---------- * 1 |DR * 5 | max(DR-2,0) * 2 |DR * 6 | max(DR-2,0) * 3 |max(DR-1,0) * 7 | max(DR-3,0) * 4 |max(DR-1,0) * 8 | max(DR-3,0) */ uint8_t confirmedNbTrials = 4; // Ack parametros de recepción uint8_t confirmaRssi = 0; uint8_t confirmaSnr = 0; uint8_t confirmaDatarate = 0; // Ack parametros de recepción uint8_t Down_rssi = 0; uint8_t Down_snr = 0; uint8_t Down_datarate = 0; uint8_t Up_rssi = 0; uint8_t itera = 0; uint8_t estado = 0; //0x00, 0x01,"OFF","ON" void setup() { Serial.begin(115200); #if(AT_SUPPORT) enableAt(); #endif // OLED display status //LoRaWAN.displayMcuInit(); deviceState = DEVICE_STATE_INIT; //LoRaWAN.ifskipjoin(); //if joinned,skip display2.init(); display2.setFont(ArialMT_Plain_10); } void loop() { Serial.print("."); itera = itera + 1; if (itera>6){ itera = 0; Serial.println(" "); } switch( deviceState ) { case DEVICE_STATE_INIT: { #if(LORAWAN_DEVEUI_AUTO) LoRaWAN.generateDeveuiByChipID(); #endif #if(AT_SUPPORT) getDevParam(); #endif printDevParam(); LoRaWAN.init(loraWanClass,loraWanRegion); deviceState = DEVICE_STATE_JOIN; break; } case DEVICE_STATE_JOIN: { //LoRaWAN.displayJoining(); displayconectando(); LoRaWAN.join(); break; } case DEVICE_STATE_SEND: { //LoRaWAN.displaySending(); displayTransmitiendo(); prepareTxFrame( appPort ); LoRaWAN.send(); deviceState = DEVICE_STATE_CYCLE; break; } case DEVICE_STATE_CYCLE: { // Schedule next packet transmission txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND ); LoRaWAN.cycle(txDutyCycleTime); deviceState = DEVICE_STATE_SLEEP; break; } case DEVICE_STATE_SLEEP: { //LoRaWAN.displayAck(); LoRaWAN.sleep(); break; } default: { deviceState = DEVICE_STATE_INIT; break; } } }
/* Prepares the payload of the frame */ static void prepareTxFrame( uint8_t port ) { // enciende sensor pinMode(Vext, OUTPUT); digitalWrite(Vext, LOW); //Lectura de Sensor //apaga sensor digitalWrite(Vext, HIGH); // lectura de bateria uint16_t batteryVoltage = getBatteryVoltage(); unsigned char *puc; // trama appDataSize = 5; appData[0] = confirmaRssi; //Ack leido en dispositivo appData[1] = confirmaSnr; appData[2] = confirmaDatarate; appData[3] = (uint8_t)batteryVoltage; appData[4] = (uint8_t)(batteryVoltage>>8); Serial.print("%, Bateria = "); Serial.println(batteryVoltage); }
//downlink data handle function example void downLinkDataHandle(McpsIndication_t *mcpsIndication){ // parametros de recepcion Down_rssi = uint8_t(abs(mcpsIndication->Rssi)); Down_snr = uint8_t(mcpsIndication->Snr); Down_datarate = uint8_t(mcpsIndication->RxDoneDatarate); // recibido de trama Up_rssi = uint8_t(mcpsIndication->Buffer[0]); // Serial.print("Rx Down_rssi:-"); Serial.print(Down_rssi); Serial.print(", Down_snr:");Serial.print(Down_snr); Serial.print(", Down_datarate: ");Serial.println(Down_datarate); Serial.print(" UP_rssi:"); Serial.print(-1*Up_rssi); Serial.printf(" +REV DATA:%s,RXSIZE %d,PORT %d\r",mcpsIndication->RxSlot?"RXWIN2":"RXWIN1",mcpsIndication->BufferSize,mcpsIndication->Port); Serial.println(); displayPaqRecibido(); }
void downLinkAckHandle(McpsIndication_t *mcpsIndication){ // ACK parametros de recepcion confirmaRssi = uint8_t(abs(mcpsIndication->Rssi)); confirmaSnr = uint8_t(mcpsIndication->Snr); confirmaDatarate = uint8_t(mcpsIndication->RxDoneDatarate); Serial.println(""); Serial.print(" ack received(rssi,snd,datarate): -"); Serial.print(confirmaRssi);Serial.print(" ,"); Serial.print(confirmaSnr);Serial.print(" ,"); Serial.println(confirmaDatarate); displayAckRecibido(); }
Ejercicio de conexión a LoRaWan con ChirpStack y HomeAssistant
// Lectura de Rssi Snr, datarate Up/Downlink // Datos Downlink de la trama de confirmación anterior // http://blog.espol.edu.ec/girni/lorawan-enlaces-up-down-archivo-ino/ #include "LoRaWan_APP.h" #include "Arduino.h" /* set LoraWan_RGB to Active,the RGB active in loraWan * red |sending; purple | joined done; * blue |RxWindow1; yellow | means RxWindow2; * green | received done; */ /* Conexión LoRa: OTAA parametros*/ uint8_t devEui[] = { 0xa5, 0x3e, 0xc6, 0x15, 0xae, 0xde, 0x3f, 0x00 }; uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t appKey[] = { 0x88, 0xbe, 0x25, 0xca, 0x2c, 0xcf, 0x31, 0x85, 0x51, 0x2d, 0xee, 0xe2, 0x80, 0x31, 0x8e, 0x01 }; /* ABP parametros*/ uint8_t nwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 }; uint8_t appSKey[] = { 0x47, 0xdc, 0xac, 0x5f, 0xc2, 0x32, 0x24, 0x31, 0xdf, 0xf1, 0xff, 0xf9, 0x46, 0xe5, 0x2e, 0x17 }; uint32_t devAddr = ( uint32_t )0x007bc4200; /*LoraWan channelsmask, default channels 0-7*/ uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 }; /*Select in arduino IDE tools*/ LoRaMacRegion_t loraWanRegion = ACTIVE_REGION; DeviceClass_t loraWanClass = LORAWAN_CLASS; bool overTheAirActivation = LORAWAN_NETMODE; bool loraWanAdr = LORAWAN_ADR; bool keepNet = LORAWAN_NET_RESERVE; bool isTxConfirmed = LORAWAN_UPLINKMODE; uint8_t duermemin = 0; //15 uint8_t duermeseg = 30; //0 uint32_t appTxDutyCycle = (duermemin*60 + duermeseg)*1000; // min*seg*ms uint8_t appPort = 4; /* Application port */ /* trials to transmit frame, if didn't receive ack. * The MAC performs a datarate adaptation, * Tx nb|Data Rate * -----|---------- * 1 |DR * 5 | max(DR-2,0) * 2 |DR * 6 | max(DR-2,0) * 3 |max(DR-1,0) * 7 | max(DR-3,0) * 4 |max(DR-1,0) * 8 | max(DR-3,0) */ uint8_t confirmedNbTrials = 4; // Ack parametros de recepción uint8_t confirmaRssi = 0; uint8_t confirmaSnr = 0; uint8_t confirmaDatarate = 0; uint8_t itera = 0; uint8_t estado = 0x00; //0x00, 0x01,"OFF","ON" void setup() { Serial.begin(115200); #if(AT_SUPPORT) enableAt(); #endif // OLED display status //LoRaWAN.displayMcuInit(); deviceState = DEVICE_STATE_INIT; //LoRaWAN.ifskipjoin(); //if joinned,skip } void loop() { Serial.print("."); itera = itera + 1; if (itera>6){ itera = 0; Serial.println(" "); } switch( deviceState ) { case DEVICE_STATE_INIT: { #if(LORAWAN_DEVEUI_AUTO) LoRaWAN.generateDeveuiByChipID(); #endif #if(AT_SUPPORT) getDevParam(); #endif printDevParam(); LoRaWAN.init(loraWanClass,loraWanRegion); deviceState = DEVICE_STATE_JOIN; break; } case DEVICE_STATE_JOIN: { //LoRaWAN.displayJoining(); LoRaWAN.join(); break; } case DEVICE_STATE_SEND: { //LoRaWAN.displaySending(); prepareTxFrame( appPort ); LoRaWAN.send(); deviceState = DEVICE_STATE_CYCLE; break; } case DEVICE_STATE_CYCLE: { // Schedule next packet transmission txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND ); LoRaWAN.cycle(txDutyCycleTime); deviceState = DEVICE_STATE_SLEEP; break; } case DEVICE_STATE_SLEEP: { //LoRaWAN.displayAck(); LoRaWAN.sleep(); break; } default: { deviceState = DEVICE_STATE_INIT; break; } } }
/* Prepares the payload of the frame */ static void prepareTxFrame( uint8_t port ) { // enciende sensor pinMode(Vext, OUTPUT); digitalWrite(Vext, LOW); //Lectura de Sensor // apaga sensor digitalWrite(Vext, HIGH); // lectura de bateria uint16_t batteryVoltage = getBatteryVoltage(); unsigned char *puc; // trama appDataSize = 5; appData[0] = confirmaRssi; //Ack leido en dispositivo appData[1] = confirmaSnr; appData[2] = confirmaDatarate; appData[3] = (uint8_t)batteryVoltage; appData[4] = (uint8_t)(batteryVoltage>>8); Serial.print("%, Bateria = "); Serial.println(batteryVoltage); }
//downlink data handle function example void downLinkDataHandle(McpsIndication_t *mcpsIndication) { // revisa parametros // Serial.print("\nLlegó un mensaje para dispositivo..."); // Serial.print("Rssi: "); // Serial.println(mcpsIndication->Rssi); // Serial.printf("+REV DATA:%s,RXSIZE %d,PORT %d\r\n", // mcpsIndication->RxSlot ? "RXWIN2" : "RXWIN1", // mcpsIndication->BufferSize, mcpsIndication->Port); // Serial.print("+REV DATA:"); // for (uint8_t i = 0; i < mcpsIndication->BufferSize; i++) { // Serial.printf("%02X", mcpsIndication->Buffer[i]); // } estado = uint8_t(mcpsIndication->Buffer[0]); Serial.print("uplink: rssi = -"); Serial.println(estado); Serial.println(); // uint32_t color = mcpsIndication->Buffer[0] << 16 | mcpsIndication->Buffer[1] << 8 | mcpsIndication->Buffer[2]; //#if (LoraWan_RGB == 1) // turnOnRGB(color, 5000); // turnOffRGB(); //#endif }
void downLinkAckHandle(McpsIndication_t *mcpsIndication){ confirmaRssi = uint8_t(abs(mcpsIndication->Rssi)); confirmaSnr = uint8_t(mcpsIndication->Snr); confirmaDatarate = uint8_t(mcpsIndication->RxDoneDatarate); //Serial.println(' '); //Serial.print(" ack received(rssi,snd,datarate): -"); //Serial.print(confirmaRssi);Serial.print(" ,"); //Serial.print(confirmaSnr);Serial.print(" ,"); //Serial.println(confirmaDatarate); }
Aunque se reciben todos los mensajes en el radio, se requiere discriminar los mensajes que son dirigidos hacia el dispositivo local.
Se modifica las intrucciones de recepción añadiendo un bloque para discriminar si el mensaje es para el dispositivo local. Esto implica descomponer la trama enviada en sus partes y convertir al tipo de dato a usar.
Las operaciones se realizan por caracter o byte con el objetivo de establecer el mecanismo a usar cuando se usa LoRaWan
Resultados obtenidos en puerto serial
TX Paquete "312159" , tamano 6 bytes , proximo en 9341 ms
RX Paquete "213169" , tamanio 6, Rssi -20
Lee mensaje enviado por: 21 valor mensaje: 69
TX Paquete "312160" , tamano 6 bytes , proximo en 8062 ms
RX Paquete "213170" , tamanio 6, Rssi -20
Lee mensaje enviado por: 21 valor mensaje: 70

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) { turnOnRGB(COLOR_RECEIVED,0); Rssi = rssi; // nivel de recepcion rxSize = size; memcpy(rxpacket, payload, size ); rxpacket[size]='\0'; //añade fin de cadena Radio.Sleep( ); Serial.printf("\r RX Paquete \"%s\" , tamanio %d, Rssi %d \r\n", rxpacket,rxSize,Rssi); //revisa direcciones char envia[4]; byte dir_envia; char recibe[4]; byte dir_recibe; char msj[4]; byte msj_valor; envia[0] = rxpacket[0]; envia[1] = rxpacket[1]; envia[2] = '\0'; //añade fin de cadena recibe[0] = rxpacket[2]; recibe[1] = rxpacket[3]; recibe[2] = '\0'; //añade fin de cadena msj[0] = rxpacket[4]; msj[1] = rxpacket[5]; msj[2] = '\0'; //añade fin de cadena //convierte a tipo de datos dir_envia = byte(atoi(envia)); dir_recibe = byte(atoi(recibe)); msj_valor = byte(atoi(msj)); // Muestra o discrimina mensaje en Serial-USB/Pantalla if (dir_recibe = dir_local){ Serial.print(" -- Lee mensaje enviado por: "); Serial.print(dir_envia); Serial.print(" valor mensaje: "); Serial.println(msj_valor); } }