Script to backup tasmota config regularly

The following script backs up the config of all Tasmota devices

  • Backs up the .dmp format for easy restoration, and .json for easy browsing / checking
  • Tries to only write the backup file if there have been any config changes. This preserves as many config versions as possible instead of just blindly overwriting the same backups. It would help if you’ve made a mistake and only noticed months later. A blind backup would’ve overwritten the old backups many times and you’ll end up with identical (but wrong) files.

Setup:

  • Install JRuby automation + JRuby scripting library

  • Download Tasmota’s decode-config and place the binary in CONF/scripts/decode-config/decode-config_linux

  • Optional: I have many tasmota devices. To help me configure all of them in a consistent manner, I wrote a things/items generator which uses a template system. It makes adding an extra channel/item to multiple things a breeze

  • For all tasmota things, create ‘ipaddress’ channel so we know their ip addresses

    Type string : ipaddress [ stateTopic="stat/<%= thingid %>/STATUS5", transformationPattern="JSONPATH:$..StatusNET.IPAddress", commandTopic="cmnd/<%= thingid %>/status" ]
    
  • Bind an item for this channel, and assign them to gTasmotaIPs group

    Group gTasmotaIPs
    
    String <%= name %>_IPAddress (gTasmotaIPs)     { channel="mqtt:topic:<%= thingid %>:ipaddress", autoupdate="false" }
    
  • Place the following scripts in CONF/automation/jsr223/ruby/personal/

# frozen_string_literal: true

require 'openhab'
require 'json'

# I password protect my tasmota web interface
# These are the credentials
TASMOTA_USER = 'XXXXXXXX'
TASMOTA_PASS = 'XXXXXXXX'

# Where to store the config backups
DEST_PATH = '/openhab/conf/scripts/tasmota-backups'
NUM_BACKUPS = 9

def current_file(device_id, ext)
  File.join(DEST_PATH, "#{device_id}.#{ext}")
end

def rotate_file(filename, number_of_backups, start_at = 1)
  return if number_of_backups > 100 # sanity check

  target_file = "#{filename}.#{start_at}"
  if File.exist?(target_file)
    if start_at < number_of_backups
      rotate_file(filename, number_of_backups, start_at + 1)
    else
      File.delete(target_file)
    end
  end
  File.rename(filename, target_file) if File.exist?(filename)
end

def new_config?(device_id, file_content)
  !File.exist?(current_file(device_id, 'json')) || File.read(current_file(device_id, 'json')) != file_content
end

def remove_transient_data(config)
  %w[cfg_crc cfg_crc32 cfg_timestamp save_flag power wifi_bssid bri_power_on light_dimmer light_color].each do |key|
    config.delete(key)
  end
  %w[crc crc32].each do |key|
    config['header']['data'].delete(key)
    config['header']['data']['template'].delete(key)
  end
  config['header'].delete('timestamp')
  config
end

def download_config(ip, dest: nil)
  cmd = "/openhab/conf/scripts/decode-config/decode-config_linux -s 'http://#{TASMOTA_USER}:#{TASMOTA_PASS}@#{ip}'"
  cmd += " -o #{dest}" if dest
  `#{cmd}`
end

def backup_tasmota(ip)
  json_data = download_config(ip)
  config = JSON.parse(json_data)
  device_id = config&.dig('mqtt_topic')

  config = remove_transient_data(config)
  json_data = config.to_json

  return if !device_id || json_data&.empty? || !new_config?(device_id, json_data)

  json_file = current_file(device_id, 'json')
  dump_file = current_file(device_id, 'dmp')
  rotate_file(json_file, NUM_BACKUPS)
  File.write(json_file, json_data)

  rotate_file(dump_file, NUM_BACKUPS)
  download_config(ip, dest: dump_file)
  logger.info "Backed up #{ip.name} config to #{json_file} and #{dump_file}"
rescue StandardError => e
  logger.error("Error backing up #{ip.name}: #{e.message}")
end

@tasmota_ips = []

## TODO: Turn on smart switches before backup
rule 'Tasmota: Backup config' do
  # on_start
  every :week
  # delay rand(20..60).minutes
  run do
    @tasmota_ips = gTasmotaIPs.select(&:state?)

    after(100.ms) do |timer|
      ip = @tasmota_ips.shift
      next unless ip

      logger.info("Backing up Tasmota config for #{ip.name}: #{ip}")
      backup_tasmota ip

      timer.reschedule unless timer.cancelled?
    end
  end
end

rule 'Tasmota: Update IP' do
  on_start
  every :hour
  run { gTasmotaIPs << '5' }
end

1 Like

That’s quite the script!

If you have any interest in doing things a little bit differently, I’d recommend you check out the esphome project. Instead of having to pull configs from all your different devices and back them up, you would configure them with config files on your computer and then build and push the images OTA to the devices using the esphome tooling. Then you can save your config code in git or something similar and it’s super easy to replace a device if one does down.

“Infrastructure as code” has been really big the last few years in the software industry. Esphome feels very similar. I think about it as “ESP boards as code.” I believe it can do pretty much everything that tasmota can do, but it’s much easier to manage IMHO.

For some reason, I never quite got to checking out esphome. I either use tasmota, or write my own firmware using platformio with homie standard. One of these days I should give esphome a try. I just had a feeling that it has far inferior features compared to tasmota. For example, I have a 3-gang switch that’s set to interlock mode. It was super easy to set up with tasmota. There are many other specific things that tasmota can do. Another example: Automatically revert its behaviour when openhab is down (I believe I made another post here about how to do this). multi tap, separation of buttons/relays, schedule, pulse, to name a few more. I use all these features in various different applications. Hard to imagine esphome able to do all this without a lot of extra effort.

I have been meaning to write a tasmota config management tool for my devices, but haven’t got around to it. The idea is to automatically configure them based on their role / type, a DIY version of Ansible, but specific to the openhab roles, e.g. as a switch for smart light bulb, etc. It’s all very specific to my installation / configuration perhaps.

Being able to have a clean and simple config would be great indeed.

I don’t know how good it used to be, but I was surprised I never heard of it when I ran across it. I was looking to implement a fairly obscure use case (Mitsubishi ductless split unit control) and there was a module in esphome for it that works great.

Definitely check out what it can do before you commit to anything, but I will say that one interesting feature it has is that you can write macros. The macros are literally c++ code (if memory serves) that get compiled directly into the firmware image when it’s built. So it’s pretty much possible to do anything you might want.

I’m a software engineer in my day job, so I often consider doing things like you’re talking about for my home automation stuff. I could do those things, but do I really want to? (sometimes yes, usually no). Esphome was a nice surprise for me with how full featured it was and how easy it was to use.

1 Like

I was curious, so I looked to see if esphome supported the things you mentioned.

It definitely supports interlock mode natively: GPIO Switch — ESPHome

It also looks like you can use an mqtt-connected condition to program multiple behaviors in the same device. MQTT Client Component — ESPHome.

Again, no pressure. I was just curious myself and thought I’d let you know what I found.

1 Like