Created
November 2, 2024 12:41
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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