""" Nextion2Text by Max Zuidberg This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. """ from string import whitespace import sys from pathlib import Path import struct from typing import List import argparse import copy import json class IndentList(list): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.indent = 4 self.indentLevel = 0 self.indentStr = " " self.emptyLinesLimit = 1 self.emptyLinesCount = 0 def appendIndent(self, newStr): newStr: str if not newStr.strip(whitespace): if self.emptyLinesCount < self.emptyLinesLimit: self.emptyLinesCount += 1 else: return else: self.emptyLinesCount = 0 self.append(self.indentLevel * self.indent * self.indentStr + newStr) def appendIndentLine(self, newStr): newStr: str if not newStr.endswith("\n"): newStr += "\n" self.appendIndent(newStr) class Component: """ How the attributes dict is organized: attributes: "encoded" name: -1: default interpretation: name: Name by which the encoded name will be replaced type: int/str/strlist/bool - how to decode the content mapping: optional: replace the values by a string 12: Optional interpretation if attribute is part of a component of type 12 """ codeEvents = { "codesload": "Preinitialize Event", "codesloadend": "Postinitialize Event", "codesdown": "Touch Press Event", "codesup": "Touch Release Event", "codesunload": "Page Exit Event", "codestimer": "Timer Event", "codesplayend": "Play Complete Event", } attributes = { "type": { "name": "Type", "struct": "i", "mapping": { #Needs to contain all types, even if some get overridden afterwards. Order here is used for sorting later 121: "Page", 52: "Variable", 54: "Number", 59: "XFloat", 116: "Text", 55: "Scrolling Text", 112: "Picture", 113: "Crop Picture", 58: "QR Code", 106: "Progress Bar", 122: "Gauge", 0: "Waveform", 1: "Slider", 98: "Button", 53: "Dual-state Button", 56: "Checkbox", 57: "Radio", 67: "Switch", 61: "Combo Box", 68: "Text Select", 62: "SLText", 4: "Audio", 60: "External Picture", 2: "Gmov", 3: "Video", 66: "Data Record", 63: "File Stream", 65: "File Browser", 109: "Hotspot", 51: "Timer", 5: "TouchCap", -1: "Unknown", }, "type": { 52: { "sta": { 0: { "mapping": { 52: "Variable (int32)", }, }, 1: { "mapping": { 52: "Variable (string)", }, }, }, }, }, }, "id": { "name": "ID", "struct": "i", }, "vscope": { "name": "Scope", "struct": "i", "mapping": { 0: "local", 1: "global", }, }, "objname": { "name": "Object Name", "struct": "s", }, "sta": { "name": "Fill", "struct": "i", "vis": True, "mapping": { 0: "crop image", 1: "solid color", 2: "image", }, "type": { 52: {#Variable "vis": False, "ignore": True, "mapping": { 0: "int32", 1: "string", }, }, 58: {#QR Code "name": "Logo overlay", "mapping": { 0: "no", 1: "yes", }, }, 121: {#Page "mapping": { 0: "no background (white)", 1: "solid color", 2: "picture", }, }, 106: {#Progress Bar "mapping": { 0: "solid color", 1: "image", }, }, }, "model": { "P": { "mapping": { 0: "crop image", 1: "solid color", 2: "image", 3: "transparent", }, "type": { 121: {# Page "mapping": { 0: "no background (transparent)", 1: "solid color", 2: "picture", }, }, }, }, }, }, "psta": { "struct": "i", "name": "Cursor Fill", "vis": True, "mapping": { 0: "circular", 1: "image", 2: "square", }, "model": { -1: { "ignore": True, }, "P": { "ignore": False, }, }, }, "style": { "name": "Style", "struct": "i", "vis": True, "mapping": { 0: "flat", 1: "border", 2: "3D down", 3: "3D up", 4: "3D auto", }, "sta": { -1: { "ignore": True, }, 1: { "ignore": False, } } }, "key": { "struct": "i", "name": "Associated Keyboard", "mapping": { 255: "none", }, }, "font": { "struct": "i", "name": "Font ID", "vis": True, }, "pw": { "struct": "i", "name": "Input Type", "vis": True, "mapping": { 0: "character", 1: "password", }, }, "val": { "name": "Value", "struct": "i", "type": { 1: { "name": "Position", }, 52: { "sta": { 1 : { "ignore": True, }, }, }, 53: { "name": "State", "mapping": { 0: "unpressed", 1: "pressed", }, }, 98: 53, 56: { "name": "State", "mapping": { 0: "unpressed", 1: "pressed", }, }, 57: 56, 122: { "name": "Angle (deg)", } }, }, "txt": { "name": "Text", "struct": "s", "type": { 52: { "sta": { 0 : { "ignore": True, }, }, }, }, }, "txt_maxl":{ "name": "Max. Text Size", "struct": "i", "type": { 52: { "sta": { 0 : { "ignore": True, }, }, }, }, }, "isbr": { "struct": "i", "name": "Word wrap", "vis": True, "mapping": { 0: "disabled", 1: "enabled", }, }, "vvs0": { "struct": "i", "name": "Significant digits left", "vis": True, "type": { 55: {# Scrolling Text "ignore": True, }, 122: {# Gauge "model": { -1: { "ignore": True, }, "P": { "name": "Head width" }, }, }, }, }, "vvs1": { "struct": "i", "name": "Significant digits right", "vis": True, "type": { 55: {# Scrolling Text "ignore": True, }, 122: { # Gauge "model": { -1: { "ignore": True, }, "P": { "name": "Center width" }, }, }, }, }, "vvs2": { "struct": "i", "vis": True, "type": { 55: {# Scrolling Text "ignore": True, }, 122: { # Gauge "model": { -1: { "ignore": True, }, "P": { "name": "Foot width" }, }, }, }, }, "vvs3": { "struct": "i", "vis": True, "type": { 55: {# Scrolling Text "ignore": True, }, }, }, "lenth": { "struct": "i", "name": "Significant digits shown", "vis": True, "mapping": { 0: "all", }, }, "format": { "struct": "i", "name": "Format", "vis": True, "mapping": { 0: "decimal", 1: "hexadecimal", 2: "decimal with digit grouping" }, "type": { 122: {# Gauge "name": "Angle offset", "mapping": dict(), #disable default mapping }, }, }, "tim": { "struct": "i", "name": "Period (ms)", "type": { -1: { "vis": True, }, 51: {# Timer "vis": False, }, }, }, "en": { "struct": "i", "name": "Enabled", "mapping": { 0: "no", 1: "yes", }, "type": { -1: { "vis": True, }, 51: {# Timer "vis": False, }, }, }, "dis": { "struct": "i", "name": "Corner Radius (Perc.)", "vis": True, "model": { -1: { "ignore": True, }, "P": { "ignore": False, "type": { 0: { # Waveform "ignore": True, }, 122: { # Gauge "ignore": True, }, }, }, }, }, "spax": { "struct": "i", "name": "Horizontal Spacing", "vis": True, }, "spay": { "struct": "i", "name": "Vertical Spacing", "vis": True, }, "xcen": { "struct": "i", "name": "Horizontal Alignment", "vis": True, "mapping": { 0: "left", 1: "center", 2: "right", }, }, "ycen": { "struct": "i", "name": "Vertical Alignment", "vis": True, "mapping": { 0: "top", 1: "center", 2: "bottom", }, }, "x": { "name": "x coordinate", "struct": "i", "vis": True, "type": { 121: {# Page "ignore": True, }, }, #"model": { # "P": { # "drag": { # 1: { # "name": "Initial x coord." # }, # }, # }, #}, }, "y": { "name": "y coordinate", "struct": "i", "vis": True, "type": { 121: {# Page "ignore": True, }, }, #"model": { # "P": { # "drag": { # 1: { # "name": "Initial y coord." # }, # }, # }, #}, }, "w": { "name": "Width", "struct": "i", "vis": True, type: { 121: {# Page "ignore": True, }, }, }, "h": { "name": "Height", "struct": "i", "vis": True, "type": { 121: {# Page "ignore": True, }, }, }, "bco": { "name": "Back. Color", "struct": "i", "vis": True, "sta": { 0: { "ignore": True, }, 2: 0, }, "type": { 53: {# Button "name": "Back. Color (Unpressed)", } } }, "bco1": { "name": "Slided Back. Color", "struct": "i", "vis": True, "sta": { 0: { "ignore": True, }, 2: 0, }, }, "bco2": { "struct": "i", "name": "Back. Color (Pressed)", "vis": True, "sta": { 0: { "ignore": True, }, 2: 0, }, }, "pco": { "name": "Font Color", "struct": "i", "vis": True, "sta": { 0: { "ignore": True, }, 2: 0, }, "type": { 58: { "name": "Foreground Color" }, 53: { "name": "Font Color (Unpressed)" }, 98: 53, }, }, "pco0": { "struct": "i", "name": "Channel 0 Color", "vis": True, }, "pco1": { "struct": "i", "name": "Channel 1 Color", "vis": True, "ch": { 1: { "ignore": True, }, }, }, "pco2": { "name": "Font Color (Pressed)", "struct": "i", "vis": True, "ignore": True, "type": { 53: { "sta": { 1: { "ignore": False, }, }, }, 98: 53, 0: {# Waveform "name": "Channel 2 Color", "ch": { 3: { "ignore": False, }, 4: { "ignore": False, }, }, }, }, }, "pco3": { "struct": "i", "name": "Channel 3 Color", "vis": True, "ignore": True, "ch": { 4: { "ignore": False, }, }, }, "pic": { "name": "Back. Picture ID", "struct": "i", "vis": True, "sta": { 0: { "ignore": True, }, 1: 0, }, "type": { 112: {#Picture "name": "Picture ID", }, 53: {#Button "name": "Background Picture ID (Unpressed)" }, 98: 53, #Dual-State Button 58: {#QR Code "sta": { 0: { "ignore": True, }, 1: { "ignore": False, }, }, }, }, }, "pic1": { "name": "Slided Back. Picture ID", "struct": "i", "vis": True, "sta": { 0: { "ignore": True, }, 1: 0, }, }, "pic2": { "name": "Back. Picture ID (Pressed)", "struct": "i", "ignore": True, "vis": True, "sta": { 0: { "ignore": True, }, 1: 0, }, "type": { 53: {#Button "ignore": False, }, 98: 53, }, }, "picc": { "name": "Cropped Back. Picture ID", "struct": "i", "vis": True, "sta": { 1: { "ignore": True, }, 2: 1, }, "type": { 53: { "name": "Cropped Back. Picture ID (Unpressed)" }, 98: 53, }, }, "picc1": { "name": "Cropped Slided Back. Picture ID", "struct": "i", "vis": True, "sta": { 1: { "ignore": True, }, 2: 1, }, }, "picc2": { "name": "Cropped Back. Picture ID (Pressed)", "struct": "i", "vis": True, "sta": { 1: { "ignore": True, }, 2: 1, }, }, "bpic": { "struct": "i", "name": "Background Picture ID", "vis": True, }, "ppic": { "struct": "i", "name": "Foreground Picture ID", "vis": True, }, "dez": { "name": "Direction", "struct": "i", "vis": True, "mapping": { 0: "horizontal", 1: "vertical", }, }, "dir": { "struct": "i", "name": "Direction", "vis": True, "type": { 55: {# Scrolling Text "mapping": { 0: "left->right", 1: "right->left", 2: "top->bottom", 3: "bottom->top", }, }, 0: {# Waveform "name": "Flow Direction", "mapping": { 0: "left->right", 1: "right->left", 2: "right aligned", }, }, }, }, "borderc": { "struct": "i", "name": "Border Color", "vis": True, "style": { -1: { "ignore": True, }, 1: { "ignore": False, }, }, }, "borderw": { "struct": "i", "name": "Border Width", "vis": True, "style": { -1: { "ignore": True, }, 1: { "ignore": False, }, }, }, "mode": { "name": "Direction", "struct": "i", "vis": True, "mapping": { 0: "horizontal", 1: "vertical", }, "type": { -1: { "ignore": True, }, 1: { "ignore": False, }, }, }, "maxval": { "name": "Upper range limit", "struct": "i", "type": { -1: { "ignore": True, }, 1: { "ignore": False, }, }, }, "minval": { "name": "Lower range limit", "struct": "i", "type": { -1: { "ignore": True, }, 1: { "ignore": False, }, }, }, "drag": { "name": "Dragging", "struct": "i", "model": { "T": { "ignore": True, }, "K": "T", "P": { "type": { 121: {# Page "ignore": True }, }, }, }, }, "disup": { "name": "Disable release event after dragging", "struct": "i", "model": { "T": { "ignore": True }, "K": "T", "P": { "type": { 121: {# Page "ignore": True }, }, "drag": { 0: { "ignore": True } } }, }, }, "aph": { "name": "Opacity", "struct": "i", "vis": True, "model": { "T": { "ignore": True, }, "K": "T", }, }, "first": { "name": "Effect Priority", "struct": "i", "vis": True, "model": { "T": { "ignore": True, }, "K": "T", "P": { "effect": { 0: { "ignore": True, }, }, }, }, }, "time": { "name": "Effect Time", "struct": "i", "vis": True, "model": { "T": { "ignore": True, }, "K": "T", "P": { "effect": { 0: { "ignore": True, }, }, }, }, }, "sendkey": { "name": "Send Component ID", "struct": "i", "mapping": { 0: "disabled", 1: "on release", 2: "on press", 3: "on press and release", }, }, "movex": { "name": "", "struct": "i", "vis": True, "ignore": True }, "movey": { "name": "", "struct": "i", "vis": True, "ignore": True }, "endx": { "name": "", "struct": "i", "vis": True, "ignore": True }, "endy": { "name": "", "struct": "i", "vis": True, "ignore": True }, "effect": { "name": "Effect", "struct": "i", "vis": True, "mapping": { 0: "load", 1: "top fly into", 2: "bottom fly into", 3: "left fly into", 4: "right fly into", 5: "top left fly into", 6: "top right fly into", 7: "bottom left fly into", 8: "bottom right fly into", 9: "fade into the gradual change", 10: "middle zoom", }, "model": { "T": { "ignore": True, }, "K": "T", }, }, "lockobj": { "name": "Locked", "struct": "i", "mapping": { 0: "no", 1: "yes", }, "type": { -1: { "ignore": True, }, 121: { "ignore": False, }, }, }, "groupid0": { "struct": "i", "ignore": True }, "groupid1": { "struct": "i", "ignore": True }, "ch": { "struct": "i", "ignore": True, "type": { 0: {# Waveform "ignore": False, }, }, "name": "Channel count", }, "gdc": { "struct": "i", "name": "Grid Color", "vis": True, }, "gdw": { "struct": "i", "name": "Grid hor. spacing", "vis": True, }, "gdh": { "struct": "i", "name": "Grid ver. spacing", "vis": True, }, "wid": { "struct": "i", "vis": True, "type": { 1: {# Slider "name": "Cursor width", "mapping": { 255: "auto", }, }, 122: {# Gauge "model": { -1: { "ignore": True, }, "P": { "ignore": False, "name": "Gauge Thickness", }, }, }, }, }, "hig": { "struct": "i", "vis": True, "type": { 1: {# Slider "name": "Cursor height", }, 122: {# Gauge "model": { -1: { "ignore": True, }, "P": { "ignore": False, "name": "Center circle dia.", }, }, }, }, }, "up": { "name": "Swide up page ID", "struct": "i", "mapping": { 255: "disabled", }, "type": { 122: { # Gauge "vis": True, "name": "Head Length", "mapping": { 32767: "auto", }, }, }, "model": { "T": { "ignore": True, }, "K": "T", }, }, "down": { "name": "Swide down page ID", "struct": "i", "mapping": { 255: "disabled", }, "type": { 122: { # Gauge "vis": True, "name": "Foot Length" }, }, "model": { "T": { "ignore": True, }, "K": "T", }, }, "left": { "name": "Swide left page ID", "struct": "i", "mapping": { 255: "disabled", }, "type": { 122: { # Gauge "vis": True, "name": "Head off-center" }, }, "model": { "T": { "ignore": True, }, "K": "T", }, }, "right": { "name": "Swide right page ID", "struct": "i", "mapping": { 255: "disabled", }, "type": { 122: { # Gauge "vis": True, "name": "Gauge Head Length" }, }, "model": { "T": { "ignore": True, }, "K": "T", }, }, "objWid": { "struct": "i", "ignore": True, }, "objHig": { "struct": "i", "ignore": True, }, "inittrue": { "struct": "i", "ignore": True, }, "molloc": { "struct": "i", "ignore": True, }, "molloc_s": { "struct": "i", "ignore": True, }, } def __init__(self, raw, modelSeries="T"): self.components = [] self.data = dict() self.rawData = dict() self.sloc = 0 self.uniqueSloc = set() self.propNameMaxLength = 0 self.raw = raw self.modelSeries = modelSeries self.loadRawProperties() def __repr__(self): repr = self.rawData["att"]["objname"] data = self.parseRawProperties(customInclude=("type",), inplace=False) if data and "Attributes" in data and Component.attributes["type"]["name"] in data["Attributes"]: repr = data["Attributes"][Component.attributes["type"]["name"]] + " " + repr return repr def getText(self, *args, **kwargs): return "".join(self.getTextLines(*args, **kwargs)) def getTextLines(self, indentLevel=0, indent=4, emptyLinesLimit=1, customExclude=("type", "objname"), **kwargs): # Initialize resulting IndentList result = IndentList() result.indentStr = " " result.indentLevel = indentLevel result.indent = indent result.emptyLinesLimit = emptyLinesLimit result.appendIndentLine(self.__repr__()) result.indentLevel += 1 # Parse rawData according to the given parameters self.parseRawProperties(customExclude=customExclude, **kwargs) if "Attributes" in self.data: result.appendIndentLine("Attributes") result.indentLevel += 1 propNameMaxLength = max([len(k) for k in self.data["Attributes"].keys()]) for prop, val in self.data["Attributes"].items(): try: val = val.replace("\r\n", "\\r\\n") except: pass line = prop.ljust(propNameMaxLength, " ") + ": " + str(val) result.appendIndentLine(line) result.indentLevel -= 1 result.appendIndentLine("") if "Events" in self.data: result.appendIndentLine("Events") result.indentLevel += 1 for event, code in self.data["Events"].items(): code: str result.appendIndentLine(event) result.indentLevel += 1 codeLines = code.split("\n") for cl in codeLines: originalLength = len(cl) clStripped = cl.lstrip(" ") clIndentLevel = (originalLength - len(clStripped)) // 2 clStripped = result.indentStr * result.indent * clIndentLevel + clStripped result.appendIndentLine(clStripped) result.indentLevel -= 1 result.appendIndentLine("") result.indentLevel -=1 result.appendIndentLine("") return result def parseRawProperties(self, customInclude=tuple(), customExclude=tuple(), includeVisual:bool=False, includeUnknown:int=0, inplace=True, emptyEvents=False, keepNames=False, keepValues=False, **kwargs): data = dict() # Model name is considered as an "attribute", too. (needed to know the right interpretation; see below) self.rawData["att"]["model"] = self.modelSeries attributes = dict() # The interpretation of any attribute can depend on other attributes. (see code below) dependencies = set(Component.attributes.keys()) dependencies.add("model") for attName, attData in self.rawData["att"].items(): if attName in customExclude: continue if attName in Component.attributes.keys(): # Build dictionary that interpretes and ignores the right attributes. # attProperties = dict() # attProperties.update(self.attributes[attName]) # Enforce deep copy attProperties = copy.deepcopy(Component.attributes[attName]) done = False while not done: done = True keys = [k for k in attProperties.keys()] for d in keys: if d in dependencies: done = False if d in self.rawData["att"]: val = self.rawData["att"][d] foundVal = True if val in attProperties[d]: i = val elif -1 in attProperties[d]: i = -1 else: foundVal = False if foundVal: while not type(attProperties[d][i]) is dict: vOld = i i = attProperties[d][i] attProperties[d].pop(vOld) attProperties.update(attProperties[d][i]) attProperties.pop(d) if customInclude and attName not in customInclude: attProperties["ignore"] = True if ("vis" in attProperties and attProperties["vis"]) and not includeVisual: attProperties["ignore"] = True if (not "ignore" in attProperties or not attProperties["ignore"]): if "name" in attProperties and not keepNames: attName = attProperties["name"] if "mapping" in attProperties and not keepValues: if attData in attProperties["mapping"]: attData = attProperties["mapping"][attData] attributes[attName] = attData elif attName != "model" and (includeUnknown or attName in customInclude): if not attName in customInclude: attName = "UNKNOWN " + attName if len(attData) > 4 or includeUnknown == 2:#raw attData = attData.decode("iso_8859_1") elif includeUnknown == 3:#hex attData = " ".join([hex(d)[2:] for d in attData]) elif len(attData) <= 4: val = 0 for b in reversed(attData): val = (val << 8) + b attData = val attributes[attName] = attData data["Attributes"] = attributes # model "attribute" is no longer needed and doesnt belong here anymore self.rawData["att"].pop("model") for attName, attData in self.rawData.items(): if not attName.startswith("codes") or (not includeUnknown and attName not in Component.codeEvents): continue if not emptyEvents and not attData: continue if not "Events" in data: data["Events"] = dict() if attName in Component.codeEvents: attName = Component.codeEvents[attName] data["Events"][attName] = attData if inplace: self.data = data else: return data def loadRawProperties(self): # First level parsing. Find all property entries index = 0 properties = list() while index < len(self.raw): length = struct.unpack_from("