Skip to content

Instantly share code, notes, and snippets.

@wolph
Created November 2, 2024 12:41
Show Gist options
  • Save wolph/af855631d0032e513914e6ba14c9260e to your computer and use it in GitHub Desktop.
Save wolph/af855631d0032e513914e6ba14c9260e to your computer and use it in GitHub Desktop.
Sonoff TX Ultimate Berry driver including sound and buzzer effects for Tasmota and automatic relay detection
import string
import persist
var PERSIST_VERSION = 2
var PIXEL_COUNT = 28
var BUTTON_COUNT = size(tasmota.get_power()) - 1
var LEDS = {
2: {
0: [11, 20],
1: [7, 24],
'count': [3, 3],
},
3: {
0: [11, 20],
1: [8, 22],
2: [6, 25],
'count': [2, 3, 2],
},
}
var PIXELS = []
var ZONES = []
PIXELS.resize(PIXEL_COUNT)
ZONES.resize(11)
if BUTTON_COUNT == 2
for i:0..4
ZONES[i] = 0
end
ZONES[5] = -1
for i:6..10
ZONES[i] = 1
end
elif BUTTON_COUNT == 3
for i:0..3
ZONES[i] = 0
end
for i:4..7
ZONES[i] = 1
end
for i:8..10
ZONES[i] = 2
end
end
class TXUltimate : Driver
static header = bytes('AA550102', -4)
var ser # create serial port object
var buffer # create a buffer to store serial data
# intialize the serial port, if unspecified Tx/Rx are GPIO 16/17
def init(tx, rx)
if !tx tx = 19 end
if !rx rx = 22 end
self.ser = serial(rx, tx, 115200, serial.SERIAL_8N1)
self.buffer = bytes(32)
self.init_persist()
tasmota.add_driver(self)
self.init_cron()
self.refresh_leds()
end
def init_cron()
tasmota.add_cron(
string.format('0 %d %d * * *', persist.night_time_minute, persist.night_time_hour),
/-> self.set_night(),
'set_night'
)
tasmota.add_cron(
string.format('0 %d %d * * *', persist.day_time_minute, persist.day_time_hour),
/-> self.set_day(),
'set_day'
)
tasmota.add_rule('Time#Initialized', /-> self.init_time())
end
def init_time()
persist.day = self.is_day()
self.init_persist()
self.refresh_leds()
print('Time initialized, is day:', persist.day)
end
def set_night()
persist.day = false
self.refresh_leds()
end
def set_day()
persist.day = true
self.refresh_leds()
end
def init_persist()
# Init persist if needed
if persist.version != PERSIST_VERSION
persist.night_time_hour = 20
persist.night_time_minute = 0
persist.day_time_hour = 8
persist.day_time_minute = 0
persist.led_on_color_day = '#0000FF'
persist.led_on_color_night = '#000033'
persist.led_off_color_day = '#000000'
persist.led_off_color_night = '#400000'
# persist.reverse = false
persist.day = self.is_day()
persist.save()
end
end
def is_day()
# Get the current local time as a time map
var time = tasmota.time_dump(tasmota.rtc()['local'])
var minutes = time['hour'] * 60 + time['min']
var night_time = persist.night_time_hour * 60 + persist.night_time_minute
var day_time = persist.day_time_hour * 60 + persist.day_time_minute
if night_time < day_time
return minutes >= day_time || minutes < night_time
else
return minutes >= day_time && minutes < night_time
end
end
def handle_touch(zone)
zone = ZONES[zone]
if zone == -1
return
end
var power = tasmota.get_power()
var power_zone = zone
if persist.reverse
power_zone = BUTTON_COUNT - zone - 1
end
var on = false
#if !persist.day
# tasmota.set_power(zone, false)
#else
on = !power[power_zone]
tasmota.set_power(power_zone, on)
#end
power[power_zone] = on
if persist.day
tasmota.cmd('buzzer 1,2')
end
self.refresh_leds(power)
if persist.day
if on
tasmota.cmd('backlog delay 3; I2SPlay +/smb_powerup.mp3')
else
tasmota.cmd('backlog delay 3; I2SPlay +/smb_pipe.mp3')
end
end
end
def refresh_leds(power)
var color_off
var color_on
if !persist.day
color_off = persist.led_off_color_night
color_on = persist.led_on_color_night
else
color_off = persist.led_off_color_day
color_on = persist.led_on_color_day
end
for i:0..PIXEL_COUNT - 1
PIXELS[i] = color_off
end
if power == nil
power = tasmota.get_power()
end
var button_leds = LEDS[BUTTON_COUNT]
print('Power', power)
for i:0..BUTTON_COUNT - 1
if !power[i]
continue
end
if persist.reverse
i = BUTTON_COUNT - i - 1
end
for led_idx:button_leds[i]
for j:led_idx..led_idx + button_leds['count'][i] - 1
PIXELS[j] = color_on
end
end
end
tasmota.cmd('Led1 ' + PIXELS.concat(' '))
end
# read serial port
def every_100ms()
while self.ser.available() > 0
self.buffer = self.ser.read() # read bytes from serial as bytes
for i:0..size(self.buffer)-1
if self.header != self.buffer[i..i+3]
continue
end
var msg = self.buffer[i..i+8]
var event = ''
var params = ''
var on;
if msg[3] == 0x02 # 02 signifies a touch event
if msg[4] == 0x01 # data length 1 is a release
if msg[5] < 0x0B
event = 'Short'
params = ',"Short":' + str(msg[5])
print('Short press zone:', msg[5])
elif msg[5] == 0x0B
event = 'Multi'
print('Multi press')
elif msg[5] > 0x0B
event = 'Long'
params = ',"Long":' + str(msg[5]-16)
print('Long press zone:', msg[5])
end
elif msg[4] == 0x02 # data length 3 is a press
event = 'Touch'
params = ',"Touch":' + str(msg[6])
var zone = int(msg[6])
print('Touch event:', msg[5], 'pos:', zone)
if msg[5] != 0x00
event = 'Dash'
params = ',"From":' + str(msg[6]) + ',"To":' + str(msg[5])
print('Mini swipe channel', msg[5], '->', msg[6])
else
self.handle_touch(zone)
end
elif msg[4] == 0x03 # data lenght 1 is a swipe
if msg[5] == 0x0C
event = 'Swipe right'
params = ',"From":' + str(msg[6]) + ',"To":' + str(msg[7])
print('Swipe left-right', msg[6], '->', msg[7])
elif msg[5] == 0x0D
event = 'Swipe left'
params = ',"From":' + str(msg[6]) + ',"To":' + str(msg[7])
print('Swipe right-left', msg[6], '->', msg[7])
end
end
var jm = string.format('{"TXUltimate":{"Action":"%s"%s}}',event,params)
tasmota.publish_result(jm, 'RESULT')
end
end
end
end
end
txu = TXUltimate()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment