From 2fa814dcba206da14246e48f01293b37573f7b13 Mon Sep 17 00:00:00 2001 From: joBr99 <29555657+joBr99@users.noreply.github.com> Date: Tue, 8 Feb 2022 20:32:26 +0100 Subject: [PATCH] added nextion berry driver with tft upload --- tasmota/nextion.be | 314 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 tasmota/nextion.be diff --git a/tasmota/nextion.be b/tasmota/nextion.be new file mode 100644 index 00000000..5c4a2098 --- /dev/null +++ b/tasmota/nextion.be @@ -0,0 +1,314 @@ +# Sonoff NSPanel Lovelance UI Serial Protocol driver by joBr99 + nextion upload protocol implementation using http range and tcpclient +# based on; +# Sonoff NSPanel Tasmota driver v0.47 | code by blakadder and s-hadinger + +class TftDownloader + var tcp + + var host + var port + var file + + var s + var b + var tft_file_size + var current_chunk + var current_chunk_start + var download_range + + def init(host, port, file, download_range) + self.tft_file_size = 0 + + self.host = host + self.port = port + self.file = file + self.download_range = download_range #32768 + end + + def download_chunk(b_start, b_length) + import string + self.tcp = tcpclient() + self.tcp.connect(self.host, self.port) + print("connected:", self.tcp.connected()) + self.s = "GET " + self.file + " HTTP/1.0\r\n" + self.s += string.format("Range: bytes=%d-%d\r\n", b_start, (b_start+b_length-1)) + print(string.format("Downloading Byte %d - %d", b_start, (b_start+b_length-1))) + self.s += "\r\n" + self.tcp.write(self.s) + + #read one char after another until we reached end of http header + var end_of_header = false + var header = "" + while !end_of_header + if self.tcp.available() > 0 + header += self.tcp.read(1) + if(string.find(header, '\r\n\r\n') != -1) + end_of_header = true + end + end + end + + var content_length = 0 + + # convert header to list + header = string.split(header, '\r\n') + for i : header.iter() + #print(i) + if(string.find(i, 'Content-Range:') != -1) + if self.tft_file_size == 0 + print(i) + self.tft_file_size = number(string.split(i, '/')[1]) + end + end + if(string.find(i, 'Content-Length:') != -1) + content_length = number(string.split(i, 16)[1]) + end + end + + + #print(content_length) + # read bytes until content_length is reached + var content = bytes() + while content.size() != content_length + if self.tcp.available() > 0 + content += self.tcp.readbytes() + end + end + #print(content.size()) + return content + end + + def get_file_size() + self.download_chunk(0, 1) + return self.tft_file_size + end + + # returns the next 4096 bytes after pos of the tft file + def next_chunk(pos) + if(self.current_chunk == nil) + print("current chunk empty") + self.current_chunk = self.download_chunk(pos, self.download_range) + self.current_chunk_start = pos + end + if(pos < self.current_chunk_start) + print("Requested pos is below start point of chunk in memory, not implemented") + end + if(pos >= (self.current_chunk_start+self.download_range)) + print("Requested pos is after the end of chunk in memory, downloading new range") + self.current_chunk = self.download_chunk(pos, self.download_range) + self.current_chunk_start = pos + end + var start_within_current_chunk = pos - self.current_chunk_start + return self.current_chunk[start_within_current_chunk..(start_within_current_chunk+4095)] + end +end + +class Nextion : Driver + + var ser + var flash_size + var flash_mode + var flash_current_byte + var tftd + var progress_percentage_last + static header = bytes('55BB') + + def init() + log("NSP: Initializing Driver") + self.ser = serial(17, 16, 115200, serial.SERIAL_8N1) + self.flash_mode = 0 + tasmota.add_driver(self) + end + + def crc16(data, poly) + if !poly poly = 0xA001 end + # CRC-16 MODBUS HASHING ALGORITHM + var crc = 0xFFFF + for i:0..size(data)-1 + crc = crc ^ data[i] + for j:0..7 + if crc & 1 + crc = (crc >> 1) ^ poly + else + crc = crc >> 1 + end + end + end + return crc + end + + def split_55(b) + var ret = [] + var s = size(b) + var i = s-1 # start from last + while i > 0 + if b[i] == 0x55 && b[i+1] == 0xBB + ret.push(b[i..s-1]) # push last msg to list + b = b[(0..i-1)] # write the rest back to b + end + i -= 1 + end + ret.push(b) + return ret + end + + # encode using custom protocol 55 BB [payload length] [payload] [crc] [crc] + def encode(payload) + var b = bytes() + b += self.header + b.add(size(payload), 1) # add size as 1 byte + b += bytes().fromstring(payload) + var msg_crc = self.crc16(b) + b.add(msg_crc, 2) # crc 2 bytes, little endian + return b + end + + # send a nextion payload + def encodenx(payload) + var b = bytes().fromstring(payload) + b += bytes('FFFFFF') + return b + end + + def sendnx(payload) + var payload_bin = self.encodenx(payload) + self.ser.write(payload_bin) + print("NSP: Sent =", payload_bin) + log("NSP: Nextion command sent = " + str(payload_bin), 3) + end + + def send(payload) + var payload_bin = self.encode(payload) + if self.flash_mode==1 + log("NSP: skipped command becuase still flashing", 3) + else + self.ser.write(payload_bin) + log("NSP: payload sent = " + str(payload_bin), 3) + end + end + + def start_flash(url) + + import string + var host + var port + var s1 = string.split(url,7)[1] + var i = string.find(s1,":") + var sa + if i<0 + port = 80 + i = string.find(s1,"/") + sa = string.split(s1,i) + host = sa[0] + else + sa = string.split(s1,i) + host = sa[0] + s1 = string.split(sa[1],1)[1] + i = string.find(s1,"/") + sa = string.split(s1,i) + port = int(sa[0]) + end + var file = sa[1] + #print(host,port,file) + + self.tftd = TftDownloader(host, port, file, 32768) + #self.tftd = TftDownloader("192.168.75.30", 8123, "/local/test.tft", 32768) + + # get size of tft file + self.flash_size = self.tftd.get_file_size() + + self.flash_mode = 1 + self.sendnx('DRAKJHSUYDGBNCJHGJKSHBDN') + self.sendnx('recmod=0') + self.sendnx('recmod=0') + self.sendnx("connect") + self.sendnx("connect") + + self.flash_current_byte = 0 + end + + def write_chunk(b_start) + var chunk = self.tftd.next_chunk(b_start) + #import string + #print(string.format("Sending Byte %d - %d with size of %d", b_start, b_start+4095, chunk.size())) + self.ser.write(chunk) + return chunk.size() + end + + def every_100ms() + import string + if self.ser.available() > 0 + var msg = self.ser.read() + if size(msg) > 0 + print("NSP: Received Raw =", msg) + if (self.flash_mode==1) + var str = msg[0..-4].asstring() + log(str, 3) + if (string.find(str,"comok 2")==0) + self.sendnx(string.format("whmi-wri %d,115200,res0",self.flash_size)) + elif (size(msg)==1 && msg[0]==0x05) + print("rec 0x05") + var x = self.write_chunk(self.flash_current_byte) + self.flash_current_byte = self.flash_current_byte + x + var progress_percentage = (self.flash_current_byte*100/self.flash_size) + if (self.progress_percentage_last!=progress_percentage) + print(string.format("Flashing Progress ( %d / %d ) [ %d ]", self.flash_current_byte, self.flash_size, progress_percentage)) + self.progress_percentage_last = progress_percentage + tasmota.publish_result(string.format("{\"Flashing\":{\"complete\": %d}}",progress_percentage), "RESULT") + end + if (self.flash_current_byte==self.flash_size) + log("NSP: Flashing complete") + self.flash_mode = 0 + end + tasmota.yield() + end + else + # Recive messages using custom protocol 55 BB [payload length] [payload] [crc] [crc] + if msg[0..1] == self.header + var lst = self.split_55(msg) + for i:0..size(lst)-1 + msg = lst[i] + var j = msg[2]+2 + msg = msg[3..j] + if size(msg) > 2 + var jm = string.format("{\"CustomRecv\":%s}",msg.asstring()) + tasmota.publish_result(jm, "RESULT") + end + end + elif msg == bytes('000000FFFFFF88FFFFFF') + log("NSP: Screen Initialized") + else + var jm = string.format("{\"nextion\":\"%s\"}",str(msg[0..-4])) + tasmota.publish_result(jm, "RESULT") + end + end + end + end + end +end + +var nextion = Nextion() + +def flash_nextion(cmd, idx, payload, payload_json) + def task() + nextion.start_flash(payload) + end + tasmota.set_timer(0,task) + tasmota.resp_cmnd_done() +end + +tasmota.add_cmd('FlashNextion', flash_nextion) + +def send_cmd(cmd, idx, payload, payload_json) + nextion.sendnx(payload) + tasmota.resp_cmnd_done() +end + +tasmota.add_cmd('Nextion', send_cmd) + +def send_cmd2(cmd, idx, payload, payload_json) + nextion.send(payload) + tasmota.resp_cmnd_done() +end + +tasmota.add_cmd('CustomSend', send_cmd2) \ No newline at end of file