Integrating LoRaWAN sensors into Home Assistant

we are quite lucky here on Jersey and have been working with LoRaWAN for some time, running our own island-wide LoRaWAN network here. The technology is amazing. We run dual core gateways that both mine Helium as well as broadcast our own public network.

From heliumtracker, you can see one of our very well positioned gateways often checks into St Malo - around 65km away. It actually often check into Rennes too - around 150km away.
image

In terms of devices, I would look at the rest of the Dragino stuff too, they are pretty solid. I have had one of their reed sensors (open close) running in a retirement complex in South Africa. On 2 x AAA batteries, it has been running now for 3 years and the battery is still above 50% despite being in relatively active use.

It’s a great technology and your write up is a good one, so I would highly encourage anyone who is interested to give it a go!

3 Likes

Great and comprehensive write-up! I’m certainly going to use parts of it to connect my UG65 LoRaWAN gateway to Home Assistant.

Very interesting project and fun to see increasing interest in LORA in this forum. I’ve been working with Chirpstack through my job and it’s a great system, but it seems a bit too extensive for home use with just a few nodes connected to it. I think the best approach would be to run something like a LORA hat on an RPi and then send the raw message directly to HA for decoding, or if decoding is done on the LORA gateway and then forwarded via MQTT to HA. However, it’s important to maintain the same type of decoding that Chirpstack currently uses, JavaScript, since the node library is now quite extensive and well-supported. I would be happy to further explore this with someone knowledgeable and able to develop it. Personally, I lack the in-depth development skills.

it could be, however every protocol (Zigbee, Zwave) in home assistant also has a controller. It is always much easier to simply spin up chirpstack and as such get the visibility of things like your codecs as well as gateway metrics as well as controlling your device keys. Otherwise, you are hacking around in arduino code trying to troubleshoot…

If you are just using 1 sensor, then yea sure, but over time, you will be adding more, so best start out in the right (scalable) approach and use the right tools for the job!

However each to their own! :slight_smile:

HI.
I would also recommend dragino. I am an absolute newby to this LoraWan world (but somehow love the idea); and they helped me a lot, when I told them that I did not want to use OTA for configuration but “AT-commands”.
I was pushed back in time to my old 300baud modem :slight_smile: times.
They also helped me with a missing data endpoint for a system of them.
all the best
Juergen

I cannot recommend Dragino enough in terms of the quality of all their products, the cost as well as the support.

From a comms protocol point of view, just remember, every wireless protocol has their pro’s and cons. If you can deal with the latency (which basically means avoid low latency use cases like automated lights) you will be very very pleasantly surprised.

I have some sensors (from dragino) that have been running for 4 years and still at 50% battery!

Dear rbuman70
I have a question to your post.

I have LPS8-N, i installed chirpstack-mqtt-forwarder, and in one of the next step you show configuration from ChirpStack network server.

How you manage to install docker on dragino gateway ?
On which firmware you were able to install it (?)

My goal is to run chirpstack server locally on dragino gateway.

Thank you.

Hi, PawelT. That is a difficult goal. The LPS8-N is designed as an embedded, dedicated device based on signal processing chips and RF processing hardware. Although supported by OpenWrt, it is unlikely that you would find a way to install ChirpStack.

From the introduction above,
“For the network server I could have used The Things Network (TTN), but I wanted a non-cloud, OpenSource solution so I went with ChirpStack. I already have a Linux server that hosts Home Assistant, zwavejs2mqtt and zigbee2mqtt as Docker containers and a Mosquitto MQTT server so adding ChirpStack was straightforward.”

I used a Linux server I already had. You might consider hosting Docker and ChirpStack on say a Raspberry Pi instead if you don’t have a suitable server.

Uiguy, I was amazed when I read your post about your LoRaWAN network and seeing the distances that it can communicate. What hardware are you using? It’s astounding considering the endpoints from Jersey and the mainland aren’t even line-of-sight. What type of antennas and power levels do you use?

Hi,

There is nothing special about the hardware, it just needs to be positioned correctly. That specific gateway is line of of sight actually.

That gateway is a HP0D from Dragino which is actually mining Helium as well as broadcasting our network (LNS) at the same time!

The setup is straight out the box (standard setup - 3dBm Antenna)

These gateways are actually just a raspberry pi with a helium/LoRaWAN HAT, so power consumption is pretty negligible

Yea, I don’t think that is possible with that Gateway. You need to install on dediccated Hardware or VM.

If you like, you are more than welcome to use our Chirpstack core for testing. PM me if you are interested and I can setup a tenant on there.

@rbauman70,
Can you please share the device (heltec) code?
I’m trying to run the examples but i’m getting a lot of errors and don’t know why.

Sure. Here’s my LoRa_GNSS.ino code.

/*
 * LoRaWAN GNSS tracker
 *
 */

#include "LoRaWan_APP.h"
#include "CayenneLPP.h"

#include <Wire.h>  
#include "HT_SSD1306Wire.h"

/* OTAA para*/
uint8_t devEui[] = { 0x22, 0x32, 0x33, 0x00, 0x00, 0x88, 0x88, 0x02 };
uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t appKey[] = { 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x66, 0x01 };

/* we're using OTAA, but these still need to be defined to compile successfully
/* ABP para*/
uint8_t nwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 };
uint8_t appSKey[] = { 0xd7, 0x2c, 0x78, 0x75, 0x8c, 0xdc, 0xca, 0xbf, 0x55, 0xee, 0x4a, 0x77, 0x8d, 0x16, 0xef,0x67 };
uint32_t devAddr =  ( uint32_t )0x007e6ae1;

/*LoraWan channelsmask*/
/* using channels 8-15 */
uint16_t userChannelsMask[6]={ 0xFF00,0x0000,0x0000,0x0000,0x0000,0x0000 };

/*LoraWan region, select in arduino IDE tools*/
LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;

/*LoraWan Class, Class A and Class C are supported*/
DeviceClass_t  loraWanClass = CLASS_A;

/*the application data transmission duty cycle.  value in [ms].*/
uint32_t appTxDutyCycle = 30000;

/*OTAA or ABP*/
bool overTheAirActivation = true;

/*ADR enable*/
bool loraWanAdr = true;


/* Indicates if the node is sending confirmed or unconfirmed messages */
bool isTxConfirmed = true;

/* Application port */
uint8_t appPort = 2;
/*!
* Number of trials to transmit the frame, if the LoRaMAC layer did not
* receive an acknowledgment. The MAC performs a datarate adaptation,
* according to the LoRaWAN Specification V1.0.2, chapter 18.4, according
* to the following table:
*
* Transmission nb | Data Rate
* ----------------|-----------
* 1 (first)       | DR
* 2               | DR
* 3               | max(DR-1,0)
* 4               | max(DR-1,0)
* 5               | max(DR-2,0)
* 6               | max(DR-2,0)
* 7               | max(DR-3,0)
* 8               | max(DR-3,0)
*
* Note, that if NbTrials is set to 1 or 2, the MAC will not decrease
* the datarate, in case the LoRaMAC layer did not receive an acknowledgment
*/
uint8_t confirmedNbTrials = 4;

// GNSS settings

// U-BLOX firmware version
#define   UBLOX_FW_VERSION  7

// Enable Serial debbug on Serial UART to see registers written
#define GNSS_DEBUG Serial

#define TX_PIN  46
#define RX_PIN  45
#define LED_PIN 35

#include "ublox_GNSS.h"

// Use hardware serial support
#define Serial_GNSS Serial1

float lat, lon, alt, acc, dist;

fixType_t fix = NO_FIX;

GNSS gnss( Serial_GNSS, UBLOX_FW_VERSION );
extern SSD1306Wire display;


typedef struct coords_s {
  float lat;
  float lon;
} COORDS;

COORDS home = { 35.90477, -78.83601 };

/* This is the first call to the display after sleep so we have to duplicate
 * LoRaWanClass::displaySending()
 */
void displayAcquiring()
{
	digitalWrite(Vext,LOW);
	display.init();
	display.setFont(ArialMT_Plain_16);
	display.setTextAlignment(TEXT_ALIGN_CENTER);
	display.clear();
	display.drawString(display.getWidth()/2, display.getHeight()/2, "ACQUIRING LOC");
	display.display();
}

void displayLocation()
{
	char temp[30];
	sprintf(temp,"dist: %3.1f km, hAcc %3.0f m",dist, acc);
	display.setFont(ArialMT_Plain_10);
	display.setTextAlignment(TEXT_ALIGN_RIGHT);
	display.drawString(128, 0, temp);
	display.display();
  delay( 2000 );
}

/* Prepares the payload of the frame */

static void prepareTxFrame( uint8_t port )
{
	/*appData size is LORAWAN_APP_DATA_MAX_SIZE which is defined in "commissioning.h".
	*appDataSize max value is LORAWAN_APP_DATA_MAX_SIZE.
	*if enabled AT, don't modify LORAWAN_APP_DATA_MAX_SIZE, it may cause system hanging or failure.
	*if disabled AT, LORAWAN_APP_DATA_MAX_SIZE can be modified, the max value is reference to lorawan region and SF.
	*for example, if use REGION_CN470, 
	*the max value for different DR can be found in MaxPayloadOfDatarateCN470 refer to DataratesCN470 and BandwidthsCN470 in "RegionCN470.h".
	*/
  gnss_acquire();

  CayenneLPP *lpp = new CayenneLPP( LORAWAN_APP_DATA_MAX_SIZE );

  lpp->reset();
  lpp->addGPS( 1, lat, lon, alt/1000 );
  lpp->addDistance( 2, acc );
  lpp->addDistance( 3, dist * 1000 );
  lpp->addDigitalOutput( 4, ( digitalRead( LED_PIN ) == HIGH ? 1 : 0 ));

  appDataSize = lpp->copy( appData );

  delete lpp;
}

void decodeDownlinkMsg( uint8_t *buf, uint8_t bufsize, uint8_t port )
{
    StaticJsonDocument<256> jsonBuffer;
    CayenneLPP lpp(0);

    JsonArray array = jsonBuffer.to<JsonArray>();
    lpp.decode( buf, bufsize, array );
    //serializeJsonPretty(array, Serial);
    //Serial.println();
    for ( JsonObject item : array ) {
    
      int lpp_type = item["type"];
      switch ( lpp_type )
      {
        case LPP_DIGITAL_OUTPUT:

          if ( item["channel"] == 4 )
          {
            int setval = LOW;
            if ( item["value"] == 1 )
            {
              setval = HIGH;
            }
            gpio_hold_dis( (gpio_num_t )LED_PIN );
            gpio_deep_sleep_hold_dis();
            pinMode(LED_PIN, OUTPUT);
            digitalWrite( LED_PIN, setval );
            gpio_deep_sleep_hold_en();
            gpio_hold_en( (gpio_num_t )LED_PIN );
          }
          break;

        default:
          break;
       } 
    }  
}

void downLinkDataHandle(McpsIndication_t *mcpsIndication)
{
	printf("+REV DATA:%s,RXSIZE %d,PORT %d\r\n",mcpsIndication->RxSlot?"RXWIN2":"RXWIN1",mcpsIndication->BufferSize,mcpsIndication->Port);
	printf("+REV DATA:");
	for(uint8_t i=0;i<mcpsIndication->BufferSize;i++)
	{
		printf("%02X",mcpsIndication->Buffer[i]);
	}
	printf("\r\n");
  if ( mcpsIndication->RxData )
  {
    decodeDownlinkMsg( mcpsIndication->Buffer, mcpsIndication->BufferSize, mcpsIndication->Port );
  }
}

RTC_DATA_ATTR bool firstrun = true;

void gnss_setup( void )
{

  Serial_GNSS.begin( 9600, SERIAL_8N1, RX_PIN, TX_PIN );

  #define ACQ_TIMEOUT     40
  #define SEARCH_PERIOD   0
  #define UPDATE_PERIOD   0
  #define ON_TIME         15

  if ( gnss.config( ON_OFF ) )
  {
    Serial.println("\nGNSS configured.");
  }
  else
  {
    Serial.println("\nGNSS configuration failed.");
  }

  /* wait for first fix */
  while ( gnss.getCoodinates(lon, lat, alt, fix, acc, 50) == 0 )
  {
    Serial.println("\nInitial fix failed.");
  }

  float distance = gnss.HaverSine( home.lat, home.lon, lat, lon );
  Serial.println("Distance to home: " + String( distance, 2 ) + " km" );


  if ( gnss.init( ON_OFF, ACQ_TIMEOUT, SEARCH_PERIOD, UPDATE_PERIOD, ON_TIME ) )
  {
    Serial.println("\nGNSS initialized.");
  }
  else
  {
    Serial.println("\nFailed to initialize GNSS module.");
  }

  gnss.conditionalOff();
}

void gnss_acquire( void )
{
  Serial.println("Get location");

  gnss.reinst( Serial_GNSS, UBLOX_FW_VERSION );

  Serial_GNSS.begin( 9600, SERIAL_8N1, RX_PIN, TX_PIN );

  gnss.wake();

  delay( 1000 );

  if ( gnss.getCoodinates(lon, lat, alt, fix, acc, 50) == 0) 
  {
    Serial.println("Failed to get coordinates, check signal, accuracy required or wiring");
  }

  Serial.println("Location lat:" + String(lat, 7) +" lon:" + String(lon, 7) +
                  + " alt: " + String( alt ) + "m horizontal accuracy: " + String(acc) + "m");

  //Serial.println("\nOr try the following link to see on google maps:");
  //Serial.println(String("https://www.google.com/maps/search/?api=1&query=") + String(lat,7) + "," + String(lon,7));

  dist = gnss.HaverSine( home.lat, home.lon, lat, lon );
  Serial.println("Distance to home: " + String( dist, 2 ) + " km" );

  gnss.conditionalOff();

}

void setup() {

  Serial.begin(115200);
  Mcu.begin();
  if(firstrun)
  {
    LoRaWAN.displayMcuInit();
    firstrun = false;

    gnss_setup();

 }

	deviceState = DEVICE_STATE_INIT;
}

void loop()
{

  switch( deviceState )
	{
		case DEVICE_STATE_INIT:
		{
#if(LORAWAN_DEVEUI_AUTO)
			LoRaWAN.generateDeveuiByChipID();
#endif
			LoRaWAN.init(loraWanClass,loraWanRegion);

      break;
		}
		case DEVICE_STATE_JOIN:
		{
      LoRaWAN.displayJoining();
			LoRaWAN.join();
			break;
		}
		case DEVICE_STATE_SEND:
		{
      displayAcquiring();
			prepareTxFrame( appPort );
      displayLocation();

      LoRaWAN.displaySending();

			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(loraWanClass);

			break;
		}
		default:
		{
			deviceState = DEVICE_STATE_INIT;
			break;
		}
	}
}

The contents of the Arduino sketch.yaml file.

default_fqbn: heltec:esp32:WIFI_LoRa_32_V3:UploadSpeed=921600,CPUFreq=240,DebugLevel=none,LORAWAN_REGION=3,LoRaWanDebugLevel=0,LoopCore=1,EventsCore=1,LORAWAN_PREAMBLE_LENGTH=0,LORAWAN_DEVEUI=1
default_port: COM5

And the decoder.js file.

/**
 * @reference https://github.com/myDevicesIoT/cayenne-docs/blob/ff6da3d400d406a3cbc6b380abe3eeaa7009bbb9/docs/LORA.md
 * @reference http://openmobilealliance.org/wp/OMNA/LwM2M/LwM2MRegistry.html#extlabel
 *
 * Adapted for lora-app-server from https://gist.github.com/iPAS/e24970a91463a4a8177f9806d1ef14b8
 *
 * Type                 IPSO    LPP     Hex     Data Size   Data Resolution per bit
 *  Digital Input       3200    0       0       1           1
 *  Digital Output      3201    1       1       1           1
 *  Analog Input        3202    2       2       2           0.01 Signed
 *  Analog Output       3203    3       3       2           0.01 Signed
 *  Illuminance Sensor  3301    101     65      2           1 Lux Unsigned MSB
 *  Presence Sensor     3302    102     66      1           1
 *  Temperature Sensor  3303    103     67      2           0.1 °C Signed MSB
 *  Humidity Sensor     3304    104     68      1           0.5 % Unsigned
 *  Accelerometer       3313    113     71      6           0.001 G Signed MSB per axis
 *  Barometer           3315    115     73      2           0.1 hPa Unsigned MSB
 *  Time                3333    133     85      4           Unix time MSB
 *  Gyrometer           3334    134     86      6           0.01 °/s Signed MSB per axis
 *  GPS Location        3336    136     88      9           Latitude  : 0.0001 ° Signed MSB
 *                                                          Longitude : 0.0001 ° Signed MSB
 *                                                          Altitude  : 0.01 meter Signed MSB
 *
 * Additional types
 *  Generic Sensor      3300    100     64      4           Unsigned integer MSB
 *  Voltage             3316    116     74      2           0.01 V Unsigned MSB
 *  Current             3317    117     75      2           0.001 A Unsigned MSB
 *  Frequency           3318    118     76      4           1 Hz Unsigned MSB
 *  Percentage          3320    120     78      1           1% Unsigned
 *  Altitude            3321    121     79      2           1m Signed MSB
 *  Concentration       3325    125     7D      2           1 PPM unsigned : 1pmm = 1 * 10 ^-6 = 0.000 001
 *  Power               3328    128     80      2           1 W Unsigned MSB
 *  Distance            3330    130     82      4           0.001m Unsigned MSB
 *  Energy              3331    131     83      4           0.001kWh Unsigned MSB
 *  Colour              3335    135     87      3           R: 255 G: 255 B: 255
 *  Direction           3332    132     84      2           1º Unsigned MSB
 *  Switch              3342    142     8E      1           0/1

 * 
 */

// lppDecode decodes an array of bytes into an array of ojects, 
// each one with the channel, the data type and the value.
function lppDecode(bytes) {
    
    var sensor_types = {
        0  : {'size': 1, 'name': 'digital_in', 'signed': false, 'divisor': 1},
        1  : {'size': 1, 'name': 'digital_out', 'signed': false, 'divisor': 1},
        2  : {'size': 2, 'name': 'analog_in', 'signed': true , 'divisor': 100},
        3  : {'size': 2, 'name': 'analog_out', 'signed': true , 'divisor': 100},
        100: {'size': 4, 'name': 'generic', 'signed': false, 'divisor': 1},
        101: {'size': 2, 'name': 'illuminance', 'signed': false, 'divisor': 1},
        102: {'size': 1, 'name': 'presence', 'signed': false, 'divisor': 1},
        103: {'size': 2, 'name': 'temperature', 'signed': true , 'divisor': 10},
        104: {'size': 1, 'name': 'humidity', 'signed': false, 'divisor': 2},
        113: {'size': 6, 'name': 'accelerometer', 'signed': true , 'divisor': 1000},
        115: {'size': 2, 'name': 'barometer', 'signed': false, 'divisor': 10},
        116: {'size': 2, 'name': 'voltage', 'signed': false, 'divisor': 100},
        117: {'size': 2, 'name': 'current', 'signed': false, 'divisor': 1000},
        118: {'size': 4, 'name': 'frequency', 'signed': false, 'divisor': 1},
        120: {'size': 1, 'name': 'percentage', 'signed': false, 'divisor': 1},
        121: {'size': 2, 'name': 'altitude', 'signed': true, 'divisor': 1},
        125: {'size': 2, 'name': 'concentration', 'signed': false, 'divisor': 1},
        128: {'size': 2, 'name': 'power', 'signed': false, 'divisor': 1},
        130: {'size': 4, 'name': 'distance', 'signed': false, 'divisor': 1000},
        131: {'size': 4, 'name': 'energy', 'signed': false, 'divisor': 1000},
        132: {'size': 2, 'name': 'direction', 'signed': false, 'divisor': 1},
        133: {'size': 4, 'name': 'time', 'signed': false, 'divisor': 1},
        134: {'size': 6, 'name': 'gyrometer', 'signed': true , 'divisor': 100},
        135: {'size': 3, 'name': 'colour', 'signed': false, 'divisor': 1},
        136: {'size': 9, 'name': 'gps', 'signed': true, 'divisor': [10000,10000,100]},
        142: {'size': 1, 'name': 'switch', 'signed': false, 'divisor': 1},
    };

    function arrayToDecimal(stream, is_signed, divisor) {

        var value = 0;
        for (var i = 0; i < stream.length; i++) {
            if (stream[i] > 0xFF)
                throw 'Byte value overflow!';
            value = (value << 8) | stream[i];
        }

        if (is_signed) {
            var edge = 1 << (stream.length) * 8;  // 0x1000..
            var max = (edge - 1) >> 1;             // 0x0FFF.. >> 1
            value = (value > max) ? value - edge : value;
        }

        value /= divisor;

        return value;

    }

    var sensors = [];
    var i = 0;
    while (i < bytes.length) {

        var s_no   = bytes[i++];
        var s_type = bytes[i++];
        if (typeof sensor_types[s_type] == 'undefined') {
            throw 'Sensor type error!: ' + s_type;
        }

        var s_value = 0;
        var type = sensor_types[s_type];
        switch (s_type) {

            case 113:   // Accelerometer
            case 134:   // Gyrometer
                s_value = {
                    'x': arrayToDecimal(bytes.slice(i+0, i+2), type.signed, type.divisor),
                    'y': arrayToDecimal(bytes.slice(i+2, i+4), type.signed, type.divisor),
                    'z': arrayToDecimal(bytes.slice(i+4, i+6), type.signed, type.divisor)
                };
                break;
            
            case 136:   // GPS Location
                s_value = {
                    'latitude': arrayToDecimal(bytes.slice(i+0, i+3), type.signed, type.divisor[0]),
                    'longitude': arrayToDecimal(bytes.slice(i+3, i+6), type.signed, type.divisor[1]),
                    'altitude': arrayToDecimal(bytes.slice(i+6, i+9), type.signed, type.divisor[2])
                };
                break;
			case 135:   // Colour
				s_value = {
                    'r': arrayToDecimal(bytes.slice(i+0, i+1), type.signed, type.divisor),
                    'g': arrayToDecimal(bytes.slice(i+1, i+2), type.signed, type.divisor),
                    'b': arrayToDecimal(bytes.slice(i+2, i+3), type.signed, type.divisor)
                };
                break;

            default:    // All the rest
                s_value = arrayToDecimal(bytes.slice(i, i + type.size), type.signed, type.divisor);
                break;
        }
        
        sensors.push({
            'channel': s_no,
            'type': s_type,
            'name': type.name,
            'value': s_value
        });

        i += type.size;

    }

    return sensors;

}

// To use with TTN
function decodeUplink(input) {

    bytes = input.bytes;
    fPort = input.fPort;

    // flat output (like original decoder):
    var response = {};
    lppDecode(bytes, 1).forEach(function(field) {
        response[field['name'] + '_' + field['channel']] = field['value'];
    });
    return {
       data: response
    };

    // field output
    //return {'fields': lppDecode(bytes, fPort)};

}

// To use with NodeRED
// Assuming msg.payload contains the LPP-encoded byte array
/*
msg.fields = lppDecode(msg.payload);
return msg;
*/
// Encode downlink function.
//
// Input is an object with the following fields:
// - data = Object representing the payload that must be encoded.
// - variables = Object containing the configured device variables.
//
// Output must be an object with the following fields:
// - bytes = Byte array containing the downlink payload.
// 
// our data looks like: {"array":[{"channel":4,"name":"digital_out","value":1}]}

function encodeDownlink(input) {

  var arr = parseExtJSON( input.data );
  var i = 0;
  var output = [];

  arr.array.forEach(( item ) => {

    switch ( item.name ) {
      case "digital_out":
        item.type = 1;
        break;
      default:
        item.type = 255;
        break;
    }

    output[i++] = item.channel;
    output[i++] = item.type;
    output[i++] = item.value;

  });

  return {
    bytes: output
  };
}

I recommend getting the LoRaWan_OLED example running on the Heltec before tackling this.

Greate,
Thanks a lot :upside_down_face:

Hi,
I am searching a „remote Lorawan button“. I want to use the button to reliably trigger a light. I do want to use a small physical button, I can carry around, I know I could use my mobile with HA, but fiddling out the mobile on my bike is not very pleasant, my idea is to press this button about 200m before my garage door, so that it starts opening, and I just drive directly in.
I found MClimate Multipurpose Button LoRaWAN | MClimate LoRaWAN Devices e.g but do not understand to to integrate it in my dragino gateway. The setup is otaa, but I have no clue how to use the dragino with otaa. All sensors I use I setup with ABP. So does anyone has a remote button locally integrated into the dragino, or succsessfully setup otaa on the dragino server? I am googling without luck since about over 8 month.

Many thanks for a pointer to something that could do this job
Jürgen

You could use zone (with conditions) and then simply trigger the garage door to open when your mobile phone enters the zone.

Whilst your use case is feasible on LoRWAN, it is not 100% suited because:

  1. LoRaWAN is better for stationary sensors

  2. Latency can sometimes be an issue

Also, check your Dragino gateway is LoRaWAN (8 channels) and not a single channel gateway. OTAA does not work on single channel gateways

Hello community,
using the TTN integration I have integrated a LoraWAN water level sensor (Dragino PS-LB), which sends the water level in cm. I can see the sensor values as an entity of the TTN integration, the values correctly arrive at HA. Unfortunately
I cannot view the historical values as a curve or bar chart, but only get a single horizontal bar with different colors:
Screenshot 2024-11-10 154235

According to my research, I have to enter the unit_of_measurement somewhere for other display variants, but I don’t know where and how. Can any of you help me?

Thank you very much!
Franz Josef

Hi, Franz. You should first check the settings for the device in the integration panel for TTN. Typically the developer has set the sensor’s attributes correctly. I do not use that integration so I cannot help you there. I do have an MQTT depth sensor that measures in “ft” as the unit_of_measurement and will display a history graph. You can also use a “customize” entry to set the unit_of_measurement for the sensor. This thread discusses a situation similar to yours with suggestions: Unit of Measurement - 2022.4.

Thank you for your fast reply. I wanted to avoid MQTT thinking it´s too complicated - but maybe I´m wrong an the TTN-integration is even more…
The customization possibility is exactly missing the “Show as” input like we can see it in the following example:

So, I have to write anything in the configuration.yaml (but don´t know what…) or deal myself with the “mqtt topic”.