mirror of
https://github.com/joBr99/nspanel-lovelace-ui.git
synced 2025-12-19 22:24:15 +01:00
added nextion berry driver with tft upload
This commit is contained in:
314
tasmota/nextion.be
Normal file
314
tasmota/nextion.be
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user