Openwrt integration

Hi,
I know that there are two integrations for routers running OpenWrt but they are hard to use. I propose new OpenWrt integration for all of us using OpenWrt. If developers could make integrations for ie. alexa or some other devices using cloud then I can strongly believe that OpenWrt integration could be made much much better and easier to use than it is know,
I would like to have integration for OpenWrt that can seamlessly add all routers running OpenWrt with traffic monitoring, connected host, changing wifi channels, turning wifi on/off, monitor channel use etc.
For now situation is that people on their own are making custom integrations to solve some problems because current home assistant integrations are just not good enough.
I know that developers can made some serious integration for OpenWrt routers in home assistant, so please do.

So a third integration would fix what you think is wrong? Seems unlikely.

There are luci and ubus integration. And I tried them both but this is far from seamlessly integrating router in home assistant. As OpenWrt is basically linux distribution I donā€™t see why shouldnā€™t there be a OpenWrt package that you can install in OpenWrt that will provide integration to home assistant for ie.
I think that those integrations should be replaced with one but one that will cover it all and will work in gui.

Scratch that itch, go for it. Does openwrt have a rest API?

Yes it does but as I said it is hard to do it. There are some projects that tried to integrate it using rpcd, mqtt, ubus but they are all basically a diecent try to integrate router. You can get some basic stuff but to go a bit deeper its very difficult.

How do you suggest HA should communicate with openwrt then?

It is hard to say for me because Iā€™m not a developer.
There is obviously api documentation. Some guy created some integration based on it but it is hard to use it.
Probably the best way to do it will be using openwrt api.

Heyā€¦ i am highly interestedā€¦ i am currently playing with openwrt and ubus callsā€¦ it is not very complicatedā€¦ but some syntaxes are not always seen at the first sightā€¦

at the moment i am able to use curl to get for example data of realtimestats of a deviceā€¦ or dsl-metricsā€¦

what i am not familiar with, is how to use this / integrate this into hassio in a good wayā€¦
(just calling a bash schript from an addon, is maybe not the best idea)

for example:

USER="root" # usualy root
PASSW="password"
HOST="http://10.10.10.10"

#getting auth-token into variable
TOKEN=$(curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "00000000000000000000000000000000", "session", "login", { "username": '\"$USER\"', "password": '\"$PASSW\"'  } ] }'  $HOST/ubus | jq -r '.result[1].ubus_rpc_session')

#realtimestats for pppoe-wan
curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ '\"$TOKEN\"', "luci", "getRealtimeStats", { "mode":"interface","device":"pppoe-wan" } ] }'  $HOST/ubus | jq -r '.result[1].result'

#dsl metrics
curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ '\"$TOKEN\"', "dsl", "metrics", {} ] }'  $HOST/ubus | jq -r '.result[1]'

@nickrout
Did you see the authentication method?
it is not HTTP-authā€¦
on openwrt ubus calls you have to get a tokenā€¦ and then use it in the next call

EDIT:

thx a lot @nickrout
i was able to get deeper into thisā€¦ for example:

command_line:
  - sensor:
      name: "RandomThing Token" 
      command: >
        curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "00000000000000000000000000000000", "session", "login", { "username": "root", "password": "password"  } ] }'  http://10.10.10.10/ubus
      value_template: "{{ value_json.result[1].ubus_rpc_session }}"
      scan_interval: 100
  - sensor:
      name: "RandomThing Sensor" 
      command: >
        curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "{{ states('sensor.randomthing_token') }}", "dsl", "metrics", {} ] }'  http://10.10.10.10/ubus | jq '.result[1] | keys'
      value_template: "{{ value_json }}"
      scan_interval: 3

this is a good startā€¦ but itā€™s still directly curl

Iā€™m currentyl using collectd with mqtt plugin. Itā€™s oke, it provide some data, but it would be much better if we have one integration that can seamlessly integrate openwrt routers in home assistant. Everything else is just some better or worst individual attempts to make this work.

at first, using mqtt Plugin or maybe node_exporter is a question about push or pullā€¦

After some days of research i am not sure if it is easy to have a general implementationā€¦ it seems, users have very indiviual settings of openwrt.

But i am still interested.

I donā€™t see why not. People are using various methods because official support is not the best, so to say. Openwrt is a basically a linux distribution and I donā€™t see a reason why not to have a good integration. As I saw there is api now. And Iā€™m pretty sure that openwrt integration could be much much better than it is today.
Writing about this, just cross my mind. We could probably use ssh integration and combine that with other stuff.
But again, this is just individual attemps to solve this problem.

Openwrt using ssh integration, defaults

Still no ā€œintegrationā€ā€¦ but i was playing with this to understand it and to get a resultā€¦

Just for your informationā€¦ this works with ā€˜HA-Multiscrapeā€™:

configuration.yaml:

command_line:
  - sensor:
      name: "OpenWRT ubus Sessiontoken" 
      command: >
        curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "00000000000000000000000000000000", "session", "login", { "username": "root", "password": "pass"  } ] }'  http://10.10.10.10/ubus
      value_template: "{{ value_json.result[1].ubus_rpc_session }}"
      scan_interval: 200


multiscrape:
  - name: "Multiscrape DSL"
    resource: http://10.10.10.10/ubus
    method: POST
    headers:
        Content-Type: application/json
    payload: '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "{{ states("sensor.openwrt_ubus_sessiontoken") }}", "dsl", "metrics", {} ] }'
    scan_interval: 5
    verify_ssl: false
    log_response: true
    sensor:
      - unique_id: dsl_atuc_vendor_id
        name: ATU-C System Vendor ID
        value_template: '{{ value_json.result[1].atu_c.vendor }}'
      - unique_id: dsl_chipset
        name: Modem Chipset
        value_template: '{{ value_json.result[1].chipset }}'
      - unique_id: dsl_firmware_version
        name: Modem Firmware Version
        value_template: '{{ value_json.result[1].firmware_version }}'
      - unique_id: dsl_api_version
        name: Modem Api Version
        value_template: '{{ value_json.result[1].api_version }}'
      - unique_id: dsl_annex_s
        name: Annex
        value_template: '{{ value_json.result[1].annex }}'
      - unique_id: dsl_line_mode_s
        name: DSL Line Mode
        value_template: '{{ value_json.result[1].mode }}'
      - unique_id: dsl_profile_s
        name: DSL Profil
        value_template: '{{ value_json.result[1].standard }}'
      - unique_id: dsl_line_state_num
        name: DSL Line State
        value_template: '{{ value_json.result[1].state_num }}'
      - unique_id: dsl_line_state_detail
        name: DSL Line State detail
        value_template: '{{ value_json.result[1].state }}'
      - unique_id: dsl_line_state
        name: DSL Line Status
        value_template: '{{ value_json.result[1].up }}'
      - unique_id: dsl_power_mode_s
        name: Power Management Mode
        value_template: '{{ value_json.result[1].power_state }}'
      - unique_id: dsl_data_rate_down
        name: Data Rate Down
        unit_of_measurement: Mbit
        value_template: '{{ value_json.result[1].downstream.data_rate / 1000000 }}'
      - unique_id: dsl_data_rate_up
        name: Data Rate Up
        unit_of_measurement: Mbit
        value_template: '{{ value_json.result[1].upstream.data_rate / 1000000}}'
      - unique_id: dsl_line_attenuation_down
        name: Line Attenuation (LATN) down
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].downstream.latn }}'
      - unique_id: dsl_line_attenuation_up
        name: Line Attenuation (LATN) up
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].upstream.latn }}'
      - unique_id: dsl_noise_margin_down
        name: Noise Margin (SNR) down
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].downstream.snr }}'
      - unique_id: dsl_noise_margin_up
        name: Noise Margin (SNR) up
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].upstream.snr }}'
      - unique_id: dsl_signal_attenuation_down
        name: Signal Attenuation (SATN) down
        value_template: '{{ value_json.result[1].downstream.satn }}'
      - unique_id: dsl_signal_attenuation_up
        name: Signal Attenuation (SATN) up
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].upstream.satn }}'
      - unique_id: dsl_actatp_down
        name: Aggregate Transmit Power(ACTATP) down
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].downstream.actatp }}'
      - unique_id: dsl_actatp_up
        name: Aggregate Transmit Power(ACTATP) up
        unit_of_measurement: dB
        value_template: '{{ value_json.result[1].upstream.actatp }}'
      - unique_id: dsl_max_data_rate_down
        name: Max. Attainable Data Rate (ATTNDR) down
        unit_of_measurement: Mbit
        value_template: '{{ value_json.result[1].downstream.attndr / 1000000 }}'
      - unique_id: dsl_max_data_rate_up
        name: Max. Attainable Data Rate (ATTNDR) up
        unit_of_measurement: Mbit
        value_template: '{{ value_json.result[1].upstream.attndr / 1000000 }}'
      - unique_id: dsl_line_uptime
        name: Line Uptime
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].uptime }}'
      - unique_id: dsl_errors_fec_near
        name: Forward Error Correction Seconds (FECS) near
        value_template: '{{ value_json.result[1].errors.near.fecs }}'
      - unique_id: dsl_errors_fec_far
        name: Forward Error Correction Seconds (FECS) far
        value_template: '{{ value_json.result[1].errors.far.fecs }}'
      - unique_id: dsl_errors_es_near
        name: Errored seconds (ES) near
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.near.es }}'
      - unique_id: dsl_errors_es_far
        name: Errored seconds (ES) far
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.far.es }}'
      - unique_id: dsl_errors_ses_near
        name: Severely Errored Seconds (SES) near
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.near.ses }}'
      - unique_id: dsl_errors_ses_far
        name: Severely Errored Seconds (SES) far
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.far.ses }}'
      - unique_id: dsl_errors_loss_near
        name: Loss of Signal Seconds (LOSS) near
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.near.loss }}'
      - unique_id: dsl_errors_loss_far
        name: Loss of Signal Seconds (LOSS) far
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.far.loss }}'
      - unique_id: dsl_errors_uas_near
        name: Unavailable Seconds (UAS) near
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.near.uas }}'
      - unique_id: dsl_errors_uas_far
        name: Unavailable Seconds (UAS) far
        unit_of_measurement: s
        value_template: '{{ value_json.result[1].errors.far.uas }}'
      - unique_id: dsl_errors_hec_near
        name: Header Error Code Errors (HEC) near
        value_template: '{{ value_json.result[1].errors.near.hec }}'
      - unique_id: dsl_errors_hec_far
        name: Header Error Code Errors (HEC) far
        value_template: '{{ value_json.result[1].errors.far.hec }}'
      - unique_id: dsl_errors_crc_p_near
        name: Non Pre-emtive CRC errors (CRC_P) near
        value_template: '{{ value_json.result[1].errors.near.crc_p }}'
      - unique_id: dsl_errors_crc_p_far
        name: Non Pre-emtive CRC errors (CRC_P) far
        value_template: '{{ value_json.result[1].errors.far.crc_p }}'
      - unique_id: dsl_errors_crcp_p_near
        name: Pre-emtive CRC errors (CRCP_P) near
        value_template: '{{ value_json.result[1].errors.near.crcp_p }}'
      - unique_id: dsl_errors_crcp_p_far
        name: Pre-emtive CRC errors (CRCP_P) far
        value_template: '{{ value_json.result[1].errors.far.crcp_p }}'
      - unique_id: dsl_errors.near.rx_corrupted
        name: x_corrupted near
        value_template: '{{ value_json.result[1].errors.near.rx_corrupted }}'
      - unique_id: dsl_errors.far.rx_corrupted
        name: x_corrupted far
        value_template: '{{ value_json.result[1].errors.far.rx_corrupted }}'
      - unique_id: dsl_errors.near.rx_uncorrected_protected
        name: rx_uncorrected_protected near
        value_template: '{{ value_json.result[1].errors.near.rx_uncorrected_protected }}'
      - unique_id: dsl_errors.far.rx_uncorrected_protected
        name: rx_uncorrected_protected far
        value_template: '{{ value_json.result[1].errors.far.rx_uncorrected_protected }}'
      - unique_id: dsl_errors.near.rx_retransmitted
        name: rx_retransmitted near
        value_template: '{{ value_json.result[1].errors.near.rx_retransmitted }}'
      - unique_id: dsl_errors.far.rx_retransmitted
        name: rx_retransmitted far
        value_template: '{{ value_json.result[1].errors.far.rx_retransmitted }}'
      - unique_id: dsl_errors.near.rx_corrected
        name: rx_corrected near
        value_template: '{{ value_json.result[1].errors.near.rx_corrected }}'
      - unique_id: dsl_errors.far.rx_corrected
        name: rx_corrected far
        value_template: '{{ value_json.result[1].errors.far.rx_corrected }}'
      - unique_id: dsl_errors.near.tx_retransmitted
        name: tx_retransmitted near
        value_template: '{{ value_json.result[1].errors.near.tx_retransmitted }}'
      - unique_id: dsl_errors.far.tx_retransmitted
        name: tx_retransmitted far
        value_template: '{{ value_json.result[1].errors.far.tx_retransmitted }}'

since i never coded with python, it will take a while to understand and to work on an integrationā€¦ if anyone starts-up, i will try my best to contributeā€¦

I am wondering what exactly is wrong with the rest integration for you. You could have what you want set up by now if you stopped moaning and got in with it.

  1. i am not moaningā€¦ i am on a learning-path with experimentsā€¦
  2. i gave it a tryā€¦ but was not able to realise a single curl-command to get the whole json string to work withā€¦ it is too big and results in an error about complaining it has more than 255 characters:
This:
  - sensor:
      name: "RandomThing Sensor" 
      command: >
        curl -sH 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": [ "{{ states('sensor.randomthing_token') }}", "dsl", "metrics", {} ] }'  http://10.10.10.10/ubus | jq '.result[1]'
      value_template: "{{ value_json }}"
      scan_interval: 3
      
      
      
results into this:

2024-06-07 20:49:11.203 ERROR (MainThread) [homeassistant.helpers.entity] Failed to set state for sensor.randomthing_sensor, fall back to unknown
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1198, in _async_write_ha_state
    hass.states.async_set(
  File "/usr/src/homeassistant/homeassistant/core.py", line 2313, in async_set
    state = State(
            ^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 1786, in __init__
    validate_state(state)
  File "/usr/src/homeassistant/homeassistant/core.py", line 223, in validate_state
    raise InvalidStateError(
homeassistant.exceptions.InvalidStateError: Invalid state with length 2610. State max length is 255 characters.


My response wasnā€™t to you.

Try restful though.

I coudnā€™t implement first step in ha, use token and when it expires renew it and get data from router using that token.
So I went with collectd + mqtt that was easier for me and my knowledge level to implement. So I did that. I got couple of dozen sensors for each router. And Iā€™m using ssh integration for all routers. But this is not done, as I would like to get some more out of it and that requires time and effort to do it.
In the end of the day everything comes back to one - if you want something done, do it yourself.

1 Like

Just found thisā€¦ maybe a good start:

I tried that but I just got a few sensors. I never managed to go any further and get some more sensors. This integration has one problem. It became unavailable if there are no devices connected to wifi. This was reported but never fixed. It looks promising but guy that initially made this integration doesnt have time or will to deal with all bugs and improvements.
For me the best solution is still using collectd + mqtt and send data to home assistant.
Iā€™m using separate file for mqtt sensors in configuration.yaml like this

mqtt: !include mqtt_sensor.yaml

I created file mqtt_sensor.yaml and create some sensor for it like this

### Openwrt config
sensor:
  - name: OpenWrt RAM buffered
    state_topic: "collectd/OpenWrt/memory/memory-buffered"
    unit_of_measurement: MB
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
    unique_id: ram_buffered
  - name: OpenWrt RAM free
    state_topic: "collectd/OpenWrt/memory/memory-free"
    unit_of_measurement: MB
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
    unique_id: ram_free
  - name: OpenWrt RAM cached
    state_topic: "collectd/OpenWrt/memory/memory-cached"
    unit_of_measurement: MB
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
    unique_id: ram_cached
  - name: OpenWrt RAM used
    state_topic: "collectd/OpenWrt/memory/memory-used"
    unit_of_measurement: MB
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
    unique_id: ram_used
# AP 2.4 Ghz
  - name: "OpenWrt 2.4 Bitrate"
    state_topic: "collectd/OpenWrt/iwinfo-phy0-ap0/bitrate"
    unit_of_measurement: "Mbps"
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
    unique_id: "OpenWrt_2.4_bitrate"
  - name: "OpenWrt 2.4 Signal Power"
    state_topic: "collectd/OpenWrt/iwinfo-phy0-ap0/signal_power"
    unit_of_measurement: "dBm"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_2.4_signal_power"
  - name: "OpenWrt 2.4 Signal Noise"
    state_topic: "collectd/OpenWrt/iwinfo-phy0-ap0/signal_noise"
    unit_of_measurement: "dBm"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_2.4_signal_noise"
  - name: "OpenWrt 2.4 Signal Quality"
    state_topic: "collectd/OpenWrt/iwinfo-phy0-ap0/signal_quality"
    unit_of_measurement: "dB"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_2.4_signal_quality"
  - name: "OpenWrt 2.4 Stations"
    state_topic: "collectd/OpenWrt/iwinfo-phy0-ap0/stations"
    unit_of_measurement: "stations"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_2.4_stations"
# AP 5 Ghz
  - name: "OpenWrt 5 Bitrate"
    state_topic: "collectd/OpenWrt/iwinfo-phy1-ap0/bitrate"
    unit_of_measurement: "Mbps"
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 1000000 }}"
    unique_id: "OpenWrt_5_bitrate"
  - name: "OpenWrt 5 Signal Power"
    state_topic: "collectd/OpenWrt/iwinfo-phy1-ap0/signal_power"
    unit_of_measurement: "dBm"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_5_signal_power"
  - name: "OpenWrt 5 Signal Noise"
    state_topic: "collectd/OpenWrt/iwinfo-phy1-ap0/signal_noise"
    unit_of_measurement: "dBm"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_5_signal_noise"
  - name: "OpenWrt 5 Signal Quality"
    state_topic: "collectd/OpenWrt/iwinfo-phy1-ap0/signal_quality"
    unit_of_measurement: "dB"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_5_signal_quality"
  - name: "OpenWrt 5 Stations"
    state_topic: "collectd/OpenWrt/iwinfo-phy1-ap0/stations"
    unit_of_measurement: "stations"
    value_template: "{{ value.split(':')[1].split('\x00')[0] }}"
    unique_id: "OpenWrt_5_stations"
# Data
  - name: OpenWrt Network traffic out
    state_topic: "collectd/OpenWrt/interface-br-lan/if_octets"
    unit_of_measurement: "Mbps"
    value_template: "{{ value.split(':')[1].split('\x00')[0] | float / 10000 }}"
    unique_id: openwrt_network_traffic_out
  - name: OpenWrt Network traffic in
    state_topic: "collectd/OpenWrt/interface-br-lan/if_octets"
    unit_of_measurement: "Mbps"
    value_template: "{{ value.split(':')[2].split('\x00')[0] | float / 10000 }}"
    unique_id: openwrt_network_traffic_in
# Uptime
  - name: "OpenWrt uptime"
    state_topic: "collectd/OpenWrt/uptime/uptime"
    value_template: >
      {% set uptime_seconds = value.split(':')[1].split('\x00')[0] | float %}
      {% set uptime_minutes = uptime_seconds / 60 %}
      {% set uptime_hours = uptime_seconds / 3600 %}
      {% if uptime_minutes < 60 %}
        {{ uptime_minutes | round(0) }} min
      {% elif uptime_hours < 24 %}
        {{ uptime_hours | round(2) }} hr
      {% else %}
        {{ (uptime_seconds / 86400) | round(2) }} days
      {% endif %}
    unique_id: "OpenWrt_uptime"

For others dumb aps sensors are basically copy/paste with different topics.
You can just reload manully created mqtt entities and they will appear in home assistant. There is no need for ha restart.
There are more sensor to add in home assistant and all can be checked out using mqtt explorer.
Using ssh integration you can get list connected devices using

service: ssh.execute_command
data:
  command: ip neigh

but currently the problem is how to integrate or create sensors from received data or just integrate it in some lovelace dashboard that will display connected devices, ip address, lease time, online, offline etc.

After a while and using ai to write correct file, there it is. It is still a working version but itā€™s working.
Ssh to your router
Create a file connected_devices.sh. I created it in /etc directory and add to it

#!/bin/sh
BROKER="Ip_ha_broker"
PORT="1883"
USER="mqtt"      
PASSWORD="mqtt_password"   

BASE_TOPIC="collectd/OpenWrt/devices"
DISCOVERY_PREFIX="homeassistant"
NODE_ID="openwrt"

for device in $(cat /proc/net/arp | grep -v "IP address" | awk '{print $1}'); do
  # Generate the topic for the device
  DEVICE_TOPIC="${BASE_TOPIC}/device_${device}"
  
  # Generate unique ID for the device
  DEVICE_ID=$(echo $device | tr '.' '_')
  
  # Remove leading zeros from DEVICE_ID for cleaner formatting
  DEVICE_ID=$(echo $DEVICE_ID | sed 's/^0*//')
  
  # Generate the payload for the device
  PAYLOAD="{\"connected_device\": \"online\"}"
  
  # Publish the payload to the MQTT broker
  mosquitto_pub -h $BROKER -p $PORT -u $USER -P $PASSWORD -t $DEVICE_TOPIC -m "$PAYLOAD"
  
  # Publish online status
  mosquitto_pub -h $BROKER -p $PORT -u $USER -P $PASSWORD -t "${DEVICE_TOPIC}/status" -m "online"
  
  # Generate sanitized unique ID for the device in Home Assistant
  UNIQUE_ID="${NODE_ID}_${DEVICE_ID}"
  
  # Publish discovery message for the sensor
  DISCOVERY_TOPIC="${DISCOVERY_PREFIX}/sensor/${NODE_ID}/${DEVICE_ID}/config"
  DISCOVERY_PAYLOAD="{
    \"name\": \"Device ${device}\",
    \"state_topic\": \"${DEVICE_TOPIC}\",
    \"value_template\": \"{{ value_json.connected_device | default('offline') }}\",
    \"availability_topic\": \"${DEVICE_TOPIC}/status\",
    \"payload_available\": \"online\",
    \"payload_not_available\": \"offline\",
    \"unique_id\": \"${UNIQUE_ID}\",
    \"device\": {
      \"identifiers\": [\"${UNIQUE_ID}\"],
      \"name\": \"Device ${device}\",
      \"model\": \"Generic Device\",
      \"manufacturer\": \"Your Manufacturer\"
    }
  }"
  mosquitto_pub -h $BROKER -p $PORT -u $USER -P $PASSWORD -t "$DISCOVERY_TOPIC" -m "$DISCOVERY_PAYLOAD"
  
  # Print a message indicating the payload has been published
  echo "Published payload: $PAYLOAD to topic: $DEVICE_TOPIC"
  echo "Published discovery message to topic: $DISCOVERY_TOPIC"
done

Donā€™t forget to make it exec, i done chmod 755 connected_devices.sh

Edit crontab file with

crontab -e

and add

* * * * * /etc/connected_devices.sh

And have to install mosquitto client for this to work.
And it is working for now. It will create mqtt devices in home assistant and show if device is online or offline. Its not perfect, it is build with ai but it is simple and it is working.