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
groupGroup 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