+ "script" : "\/\/ License: Personal use only. See LICENSE for details.\n\/\/ This script was created by Flopp999\n\/\/ Support me with a coffee https:\/\/www.buymeacoffee.com\/flopp999 \nlet version = 0.24\nlet token;\nlet deviceSn;\nlet widget;\nlet day;\nlet date;\nlet language;\nlet settings = {}\nlet langId;\nlet hour;\nlet minute;\nlet translationData;\nlet currentLang;\n\nconst fileNameSettings = Script.name() + \"_Settings.json\";\nconst fileNameTranslations = Script.name() + \"_Translations.json\";\nconst fileNameData = Script.name() + \"_Data.json\";\nconst fileNameDataYear = Script.name() + \"_DataYear.json\";\nconst fm = FileManager.iCloud();\nconst dir = fm.documentsDirectory();\nconst filePathSettings = fm.joinPath(dir, fileNameSettings);\nconst filePathTranslations = fm.joinPath(dir, fileNameTranslations);\nconst filePathData = fm.joinPath(dir, fileNameData);\nconst filePathdataYear = fm.joinPath(dir, fileNameDataYear);\n\nif (!config.runsInWidget){\n\tawait downLoadFiles();\n\tawait updatecode();\n\tawait readTranslations();\n\tawait readsettings();\n\tawait createVariables();\n\t\/\/await start();\n}\n\nif (config.runsInWidget){\n\tawait readsettings();\n}\nif (config.runsInWidget){\n\tawait updatecode();\n\tawait createVariables();\n}\n\nasync function start() {\n\tconst [topType, topDay] = settings.showattop.split(\",\").map(s => s.trim());\n\tconst [middleType, middleDay] = settings.showatmiddle.split(\",\").map(s => s.trim());\n\t\/\/ const [bottomType, bottomDay] = settings.showatbottom.split(\",\").map(s => s.trim());\n\tlet alert = new Alert();\n\talert.message = \n\tt(\"changesetup\") + \"?\\n\" +\n\tt(\"top\").charAt(0).toUpperCase() + t(\"top\").slice(1) + \":\\n\" + t(topType) + (topDay ? \", \" + t(topDay) : \"\") + \"\\n\" +\n\tt(\"middle\").charAt(0).toUpperCase() + t(\"middle\").slice(1) + \":\\n\" + t(middleType) + (middleDay ? \", \" + t(middleDay) : \"\")\n\t\/\/t(\"bottom\").charAt(0).toUpperCase() + t(\"bottom\").slice(1) + \":\\n\" + t(bottomType) + (bottomDay ? \", \" + t(bottomDay) : \"\")\n\talert.addAction(t(\"yes\"));\n\talert.addAction(t(\"no\"));\n\tlet index = await alert.presentAlert();\n\tif (index === 0) {\n\t\tsettings = await ask();\n\t\tfm.writeString(filePathSettings, JSON.stringify(settings, null, 2)); \/\/ Pretty print\n\t}\n}\n\nasync function downLoadFiles() {\n\tconst baseUrl = \"https:\/\/raw.githubusercontent.com\/flopp999\/Scriptable-Growatt\/main\/assets\/\"\n\tconst filesToDownload = [\n\t\t\"soc.png\",\n\t\t\"charge.png\",\n\t\t\"discharge.png\",\n\t\t\"export.png\",\n\t\t\"home.png\",\n\t\t\"import.png\",\n\t\t\"solar.png\"\n\t]\n\t\/\/ Ladda ner varje fil\n\tfor (let filename of filesToDownload) {\n\t\tconst url = baseUrl + filename\n\t\tconst filePath = fm.joinPath(dir, filename)\n\t\ttry {\n\t\t\tconst req = new Request(url)\n\t\t\treq.timeoutInterval = 5\n\t\t\tconst image = await req.loadImage()\n\t\t\tfm.writeImage(filePath, image)\n\t\t} catch (error) {\n\t\t\tconsole.error(`Fel vid nedladdning av ${filename}:`, error)\n\t\t}\n\t}\n}\n\nasync function updatecode() {\n\ttry {\n\t\tconst req = new Request(\"https:\/\/github.com\/flopp999\/Scriptable-Growatt\/releases\/latest\/download\/Version.txt\");\n\t\treq.timeoutInterval = 1;\n\t\tconst serverVersion = await req.loadString()\n\t\tif (version < serverVersion) {\n\t\t\ttry {\n\t\t\t\tconst req = new Request(\"https:\/\/github.com\/flopp999\/Scriptable-Growatt\/releases\/latest\/download\/Growatt.js\");\n\t\t\t\treq.timeoutInterval = 1;\n\t\t\t\tconst response = await req.load();\n\t\t\t\tconst status = req.response.statusCode;\n\t\t\t\tif (status !== 200) {\n\t\t\t\t\tthrow new Error(`Error: HTTP ${status}`);\n\t\t\t\t}\n\t\t\t\tconst codeString = response.toRawString();\n\t\t\t\tfm.writeString(module.filename, codeString);\n\t\t\t\t\n\t\t\t\tconst reqTranslations = new Request(\"https:\/\/github.com\/flopp999\/Scriptable-Growatt\/releases\/latest\/download\/Translations.json\");\n\t\t\t\treqTranslations.timeoutInterval = 1;\n\t\t\t\tconst responseTranslations = await reqTranslations.load();\n\t\t\t\tconst statusTranslations = reqTranslations.response.statusCode;\n\t\t\t\tif (statusTranslations !== 200) {\n\t\t\t\t\tthrow new Error(`Error: HTTP ${statusTranslations}`);\n\t\t\t\t}\n\t\t\t\tconst codeStringTranslations = responseTranslations.toRawString();\n\t\t\t\tfm.writeString(filePathTranslations, codeStringTranslations);\n\t\t\t\t\/\/fm.remove(filePathSettings);\n\t\t\t\tlet updateNotify = new Notification();\n\t\t\t\tupdateNotify.title = Script.name();\n\t\t\t\tupdateNotify.body = \"New version installed, \" + serverVersion;\n\t\t\t\tupdateNotify.sound = \"default\";\n\t\t\t\tawait updateNotify.schedule();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(error);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"The update failed. Please try again later.\" + error);\n\t}\n}\n\nasync function readsettings() {\n\ttry {\n\t\tif (fm.fileExists(filePathSettings)) {\n\t\t\tlet raw = fm.readString(filePathSettings);\n\t\t\tsettings = JSON.parse(raw);\n\t\t\tif (!settings.token || settings.token.length === 0) {\n\t\t\t\tsettings.token = \"token\"\n\t\t\t}\n\t\t\tif (!settings.deviceSn || settings.deviceSn.length === 0) {\n\t\t\t\tsettings.deviceSn = \"deviceSn\"\n\t\t\t}\n\t\t\tif (!settings.updatehour || settings.updatehour.length === 0) {\n\t\t\t\tsettings.updatehour = \"0\"\n\t\t\t}\n\t\t\tif (!settings.updateminute || settings.updateminute.length === 0) {\n\t\t\t\tsettings.updateminute = \"01\"\n\t\t\t}\n\t\t\tif (!settings.language || settings.language.length === 0) {\n\t\t\t\tsettings.language = 1\n\t\t\t}\n\t\t\tlangId = settings.language; \/\/ 1 = ENG, 2 = DE, 3 = SV\n\t\t\tawait readTranslations();\n\t\t} else {\n\t\t\tif (config.runsInWidget) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait askForLanguage();\n\t\t\tawait readTranslations();\n\t\t\tlet alert = new Alert();\n\t\t\talert.title = \"Support\";\n\t\t\talert.message = t(\"buymeacoffee\") + \"?\";\n\t\t\talert.addAction(t(\"noway\"));\n\t\t\talert.addAction(\"Ko-fi\");\n\t\t\talert.addCancelAction(\"Buymeacoffee\");\n\t\t\tlet response = await alert.present();\n\t\t\tif (response === -1) {\n\t\t\t\tSafari.open(\"https:\/\/buymeacoffee.com\/flopp999\");\n\t\t\t}\n\t\t\tif (response === 1) {\n\t\t\t\tSafari.open(\"https:\/\/ko-fi.com\/flopp999\");\n\t\t\t}\n\t\t\tthrow new Error(\"Settings file not found\");\n\t\t}\n\t} catch (error) {\n\tif (config.runsInWidget) {\n\t\treturn;\n\t}\n\tconsole.warn(\"Settings file not found or error reading file: \" + error.message);\n\tsettings = await ask();\n\tfm.writeString(filePathSettings, JSON.stringify(settings, null, 2)); \/\/ Pretty print\n\t}\n}\n\nasync function fetchData(jwtToken) {\n\tPath = filePathData\n\tDateObj = new Date();\n\tasync function getData() {\n\t\tconst url = \"https:\/\/openapi.growatt.com\/v1\/device\/tlx\/tlx_last_data\";\n\t\tlet req = new Request(url);\n\t\treq.method = \"POST\";\n\t\treq.headers = {\n\t\t\t\"Content-Type\": \"application\/x-www-form-urlencoded\",\n\t\t\t\"token\": token\n\t\t};\n\t\treq.body = `tlx_sn=${encodeURIComponent(deviceSn)}`;\n\t\ttry {\n\t\t\treq.timeoutInterval = 1;\n\t\t\tconst response = await req.loadJSON();\n\t\t\tif (req.response.statusCode === 200) {\n\t\t\t\tconst dataJSON = JSON.stringify(response, null ,2);\n\t\t\t\tfm.writeString(filePathData, dataJSON);\n\t\t\t\tconsole.log(\"Svar från Growatt:\", response);\n\t\t\t\tsettings.updatehour = String(DateObj.getHours()).padStart(2,\"0\");\n\t\t\t\tsettings.updateminute = String(DateObj.getMinutes()).padStart(2,\"0\");\n\t\t\t\tfm.writeString(filePathSettings, JSON.stringify(settings, null, 2)); \/\/ Pretty print\n\t\t\t} else {\n\t\t\t\tconsole.error(\"❌ Fel statuskod:\", req.response.statusCode);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconsole.error(\"Fel vid API-anrop:\", err);\n\t\t}\n\t}\n\t\n\tif (fm.fileExists(filePathData)) {\n\t\tlet modified = fm.modificationDate(filePathData);\n\t\tlet now = new Date();\n\t\tlet minutesDiff = (now - modified) \/ (1000 * 60);\n\t\tif ( minutesDiff > 10 ) {\n\t\t\tawait getData();\n\t\t}\n\t} else {\n\t\tawait getData();\n\t}\n\tlet content = fm.readString(filePathData);\n\tdata = JSON.parse(content);\n\tepv1 = data[\"data\"][\"epv1Today\"];\n\tepv2 = data[\"data\"][\"epv2Today\"];\n\tbatterysoc = data[\"data\"][\"bmsSoc\"];\n\thomekwh = data[\"data\"][\"elocalLoadToday\"];\n\texportkwh = data[\"data\"][\"etoGridToday\"];\n\timportkwh = data[\"data\"][\"etoUserToday\"];\n\tbatterychargekwh = data[\"data\"][\"echargeToday\"];\n\tbatterydischargekwh = data[\"data\"][\"edischargeToday\"];\n\tupdated = \"\" + hour + minute + \"\";\n}\n\nasync function createVariables() {\n\ttoken = settings.token;\n\tdeviceSn = settings.deviceSn;\n\thour = settings.updatehour;\n\tminute = settings.updateminute;\n}\n\nasync function readTranslations() {\n\tif (!fm.fileExists(filePathTranslations)) {\n\t\tlet url = \"https:\/\/github.com\/flopp999\/Scriptable-Growatt\/releases\/latest\/download\/Translations.json\";\n\t\tlet req = new Request(url);\n\t\treq.timeoutInterval = 1;\n\t\tlet content = await req.loadString();\n\t\tfm.writeString(filePathTranslations, content);\n\t}\n\ttry {\n\t\ttranslationData = JSON.parse(fm.readString(filePathTranslations));\n\t\tconst langMap = {\n\t\t\t1: \"en\",\n\t\t\t3: \"sv\"\n\t\t};\n\t\tcurrentLang = langMap[langId] || \"en\"; \/\/ fallback to english\n\t} catch (error) {\n\t\tconsole.error(error);\n\t}\n}\n\nfunction t(key) {\n\tconst entry = translationData[key];\n\tif (!entry) return `[${key}]`; \/\/ key is missing\n\treturn entry[currentLang] || entry[\"en\"] || `[${key}]`;\n}\n\nasync function ask() {\n\tsettings.token = await askForToken();\n\tsettings.deviceSn = await askForDeviceSn();\n\treturn settings\n}\n\n\/\/ Select resolution\nasync function askForLanguage() {\n\tlet alert = new Alert();\n\talert.message = \"Language\/Språk:\";\n\talert.addAction(\"English\");\n\talert.addAction(\"Svenska\");\n\tlet index = await alert.presentAlert();\n\tsettings.language = [1,3][index];\n\tfm.writeString(filePathSettings, JSON.stringify(settings, null, 2)); \/\/ Pretty print\n\tlangId = settings.language; \/\/ 1 = ENG, 2 = DE, 3 = SV\n\treturn [1,3][index];\n}\n\n\/\/ Include extra cost?\nasync function askForToken() {\n\tlet alert = new Alert();\n\talert.title = \"Token\";\n\talert.message = (t(\"askfortoken\") + \"?\");\n\talert.addTextField(\"abc123abc123abc123\",settings.token).setDefaultKeyboard();\n\talert.addAction(\"OK\");\n\tawait alert.present();\n\tlet input = alert.textFieldValue(0);\n\ttoken = input\n\treturn input;\n}\n\n\/\/ Include extra cost?\nasync function askForDeviceSn() {\n\tlet alert = new Alert();\n\talert.title = (\"Serial number\");\n\talert.message = (t(\"askfordevicesn\") + \"?\");\n\talert.addTextField().setDefaultKeyboard();\n\talert.addAction(\"OK\");\n\tawait alert.present();\n\tlet input = alert.textFieldValue(0);\n\tdeviceSn = input;\n\treturn input;\n}\n\nconst smallFont = 10;\nconst mediumFont = 12;\nconst bigFont = 13.5;\nconst now = new Date();\n\/\/ Hämta antalet dagar i innevarande månad\nconst daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();\n\/\/ Skapa array från 1 till antal dagar\nconst daysArray = Array.from({ length: daysInMonth }, (_, i) => i + 1);\n\nasync function renderSection(position) {\n\tconst value = settings[`showat${position}`];\n\tif (!value || value === \"nothing\") return;\n\t\n\tconst [type, day] = value.split(\",\").map(s => s.trim());\n\t\n\tconst graphOption = settings.graphOption[position]\n\tswitch (type) {\n\t\tcase \"status\":\n\t\tawait Status(day);\n\t\tbreak;\n\t\tcase \"graph\":\n\t\tawait Graph(day, graphOption);\n\t\tbreak;\n\t\tcase \"revenue\":\n\t\tawait Revenue();\n\t\tbreak;\n\t\tdefault:\n\t}\n}\n\nlet listwidget = new ListWidget();\n\nasync function createWidget(){\n\t\/\/token = set loginAndGetToken();\n\tawait fetchData(token);\n\tconst date = new Date();\n\tlet solarkwh = epv1+epv2\n\t\/\/let widget = new ListWidget();\n\tlet first = listwidget.addStack()\n\tfirst.layoutHorizontally()\n\tfirst.addSpacer()\n\tlet exportrowr = first.addStack()\n\tlet exportrow=exportrowr.addStack()\n\texportrow.layoutVertically()\n\t\/\/let homerow = widget.addStack()\n\t\/\/let batterychargerow = widget.addStack()\n\t\/\/let batterydischargerow = widget.addStack()\n\tfirst.addSpacer()\n\tlet fm = FileManager.iCloud()\n\tlet exportpath = fm.joinPath(fm.documentsDirectory(), \"export.png\")\n\texportimage = await fm.readImage(exportpath)\n\tlet importpath = fm.joinPath(fm.documentsDirectory(), \"import.png\")\n\timportimage = await fm.readImage(importpath)\n\tlet solarpath = fm.joinPath(fm.documentsDirectory(), \"solar.png\")\n\tsolarimage = await fm.readImage(solarpath)\n\tlet homepath = fm.joinPath(fm.documentsDirectory(), \"home.png\")\n\thomeimage = await fm.readImage(homepath)\n\tlet batterychargepath = fm.joinPath(fm.documentsDirectory(), \"charge.png\")\n\tbatterychargeimage = await fm.readImage(batterychargepath)\n\tlet batterydischargepath = fm.joinPath(fm.documentsDirectory(), \"discharge.png\")\n\tbatterydischargeimage = await fm.readImage(batterydischargepath)\n\tlet batterysocpath = fm.joinPath(fm.documentsDirectory(), \"soc.png\")\n\tbatterysocimage = await fm.readImage(batterysocpath)\n\t\n\texportrow.addSpacer()\n\tkk=exportrow.addImage(solarimage);\n\tkk.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\texportrow.addSpacer()\n\tss=exportrow.addImage(homeimage);\n\tss.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\texportrow.addSpacer()\n\tii=exportrow.addImage(exportimage);\n\tii.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\texportrow.addSpacer()\n\tpp=exportrow.addImage(importimage);\n\tpp.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\texportrow.addSpacer()\n\tde=exportrow.addImage(batterychargeimage);\n\tde.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\texportrow.addSpacer()\n\tll=exportrow.addImage(batterydischargeimage);\n\tll.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\texportrow.addSpacer()\n\tl=exportrow.addImage(batterysocimage);\n\tl.imageSize = new Size(40, 40); \/\/ Extra kontroll på bildstorlek\n\t\/\/exportrow.addSpacer()\n\texportrowr.addSpacer(10)\n\t\n\tlet exportvalue = exportrowr.addStack()\n\texportvalue.layoutVertically()\n\t\n\texportvalue.addSpacer(15)\n\tlet solarkwhtext = exportvalue.addText(Math.round(solarkwh) + \" kWh\");\n\texportvalue.addSpacer(23)\n\tlet homewhtext = exportvalue.addText(Math.round(homekwh) + \" kWh\");\n\texportvalue.addSpacer(23)\n\tlet exportkwhtext = exportvalue.addText(Math.round(exportkwh) + \" kWh\");\n\texportvalue.addSpacer(23)\n\tlet importkwhtext = exportvalue.addText(Math.round(importkwh) + \" kWh\");\n\texportvalue.addSpacer(23)\n\tlet batterychargekwhtext = exportvalue.addText(Math.round(batterychargekwh) + \" kWh\");\n\texportvalue.addSpacer(23)\n\tlet batterydischargekwhtext = exportvalue.addText(Math.round(batterydischargekwh) + \" kWh\");\n\texportvalue.addSpacer(23)\n\tlet batterysoctext = exportvalue.addText(Math.round(batterysoc) + \" %\");\n\t\n\tsolarkwhtext.textColor = new Color(\"#ffffff\");\n\thomewhtext.textColor = new Color(\"#ffffff\");\n\texportkwhtext.textColor = new Color(\"#ffffff\");\n\timportkwhtext.textColor = new Color(\"#ffffff\");\n\tbatterychargekwhtext.textColor = new Color(\"#ffffff\");\n\tbatterydischargekwhtext.textColor = new Color(\"#ffffff\");\n\tbatterysoctext.textColor = new Color(\"#ffffff\");\n\t\n\tlistwidget.backgroundColor = new Color(\"#000000\");\n\tawait renderSection(\"top\");\n\t\/\/await renderSection(\"middle\");\n\t\/\/await renderSection(\"bottom\"); \n\tlistwidget.addSpacer(0);\n\tlet moms = listwidget.addStack();\n\tmomstext = moms.addText(\"v. \" + version);\n\tmomstext.font = Font.lightSystemFont(10);\n\tmomstext.textColor = new Color(\"#ffffff\");\n\tmoms.addSpacer();\n\tmomstext = moms.addText(\"updated \" + settings.updatehour + \":\" + settings.updateminute);\n\tmomstext.font = Font.lightSystemFont(10);\n\tmomstext.textColor = new Color(\"#ffffff\");\n\treturn listwidget;\n}\n\nwidget = await createWidget();\n\nif (config.runsInWidget) {\n\tScript.setWidget(widget);\n} else {\n\tif (Math.random() < 0.5) {\n\t\tlet alert = new Alert();\n\t\talert.title = \"Support\";\n\t\talert.message = t(\"buymeacoffee\") + \"?\";\n\t\talert.addCancelAction(\"Buymeacoffee 👍\");\n\t\talert.addAction(\"Ko-fi 👍\");\n\t\talert.addAction(t(\"noway\"));\n\t\tlet response = await alert.present();\n\t\tif (response === -1) {\n\t\t\tSafari.open(\"https:\/\/buymeacoffee.com\/flopp999\");\n\t\t}\n\t\tif (response === 0) {\n\t\t\tSafari.open(\"https:\/\/ko-fi.com\/flopp999\");\n\t\t}\n\t}\n}\n\nwidget.presentLarge()\nScript.complete();\n",
0 commit comments