Advertisement
Guest User

mojelektro-scriptable-v1

a guest
Jan 23rd, 2025
93
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.80 KB | None | 0 0
  1. // Variables used by Scriptable.
  2. // These must be at the very top of the file. Do not edit.
  3. // icon-color: deep-blue; icon-glyph: calculator;
  4.  
  5. /*
  6. Electricity consumption widget for Slovenian users.
  7. */
  8.  
  9. // parameter in format MEASURING_POINT|API_TOKEN
  10. let param = args.widgetParameter ? args.widgetParameter.split('|') : null;
  11. if (param === null || param.length !== 2) {
  12. param = [
  13. '2-11111',
  14. '604APITOKENfa'
  15. ]
  16. }
  17.  
  18. const measuringPoint = param[0];
  19. const apiToken = param[1];
  20.  
  21. const minPowerConsumption = 3.8
  22.  
  23. const blocks = {
  24. T00_06: {
  25. duration: '22h - 6h',
  26. weekdays: { ht: 3, lt: 4 },
  27. weekends: { ht: 4, lt: 5 }
  28. },
  29. T06_07: {
  30. duration: '6h - 7h',
  31. weekdays: { ht: 2, lt: 3 },
  32. weekends: { ht: 3, lt: 4 }
  33. },
  34. T07_14: {
  35. duration: '7h - 14h',
  36. weekdays: { ht: 1, lt: 2 },
  37. weekends: { ht: 2, lt: 3 }
  38. },
  39. T14_16: {
  40. duration: '14h - 16h',
  41. weekdays: { ht: 2, lt: 3 },
  42. weekends: { ht: 3, lt: 4 }
  43. },
  44. T16_20: {
  45. duration: '16h - 20h',
  46. weekdays: { ht: 1, lt: 2 },
  47. weekends: { ht: 2, lt: 3 }
  48. },
  49. T20_22: {
  50. duration: '20h - 22h',
  51. weekdays: { ht: 2, lt: 3 },
  52. weekends: { ht: 3, lt: 4 }
  53. },
  54. T22_24: {
  55. duration: '22h - 6h',
  56. weekdays: { ht: 3, lt: 4 },
  57. weekends: { ht: 4, lt: 5 }
  58. }
  59. }
  60.  
  61. const slovenian_holidays = [
  62. '-01-01', // Novo leto
  63. '-01-02', // Novo leto
  64. '-02-08', // Prešernov dan
  65. '-04-27', // Dan upora proti okupatorju
  66. '-05-01', // Praznik dela
  67. '-05-02', // Praznik dela
  68. '-06-25', // Dan državnosti
  69. '-08-15', // Marijino vnebovzetje
  70. '-10-31', // Dan reformacije
  71. '-11-01', // Dan spomina na mrtve
  72. '-12-25', // Božič
  73. '-12-26' // Dan samostojnosti in enotnosti
  74. ]
  75.  
  76. let easter_monday = function(year) {
  77. let a = year % 19;
  78. let b = Math.floor(year / 100);
  79. let c = year % 100;
  80. let d = Math.floor(b / 4);
  81. let e = b % 4;
  82. let f = Math.floor((b + 8) / 25);
  83. let g = Math.floor((b - f + 1) / 3);
  84. let h = (19 * a + b - d - g + 15) % 30;
  85. let i = Math.floor(c / 4);
  86. let k = c % 4;
  87. let l = (32 + 2 * e + 2 * i - h - k) % 7;
  88. let m = Math.floor((a + 11 * h + 22 * l) / 451);
  89. let month = Math.floor((h + l - 7 * m + 114) / 31);
  90. let day = ((h + l - 7 * m + 114) % 31) + 1;
  91. return new Date(year, month - 1, day + 1);
  92. }
  93.  
  94. low_tariff_months_start = 3
  95. low_tariff_months_end = 10
  96.  
  97. let holidays = slovenian_holidays.map(holiday => {
  98. return new Date(new Date().getFullYear() + holiday)
  99. })
  100. holidays.push(easter_monday(new Date().getFullYear()))
  101.  
  102. if (config.runsInWidget) {
  103. const size = config.widgetFamily;
  104. const widget = await createWidget(size);
  105.  
  106. Script.setWidget(widget);
  107. Script.complete();
  108. } else {
  109. // For debugging
  110. const size = 'small';
  111. //const size = 'medium'
  112. //const size = 'large'
  113. const widget = await createWidget(size);
  114. if (size == 'small') {
  115. widget.presentSmall();
  116. } else if (size == 'medium') {
  117. widget.presentMedium();
  118. } else {
  119. widget.presentLarge();
  120. }
  121. Script.complete();
  122. }
  123.  
  124. async function createWidget(size) {
  125.  
  126. let prev_dates = {
  127. fromDate: new Date(new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1).setHours(12,0,0,0)).toISOString().split("T")[0],
  128. toDate: new Date(new Date(new Date().getFullYear(), new Date().getMonth(), 0).setHours(12,0,0,0)).toISOString().split("T")[0]
  129. }
  130. let curr_dates = {
  131. fromDate: new Date(new Date(new Date().getFullYear(), new Date().getMonth(), 1).setHours(12,0,0,0)).toISOString().split("T")[0],
  132. toDate: new Date(new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).setHours(12,0,0,0)).toISOString().split("T")[0]
  133. }
  134. const pData = await fetchData(measuringPoint, apiToken, prev_dates.fromDate, prev_dates.toDate);
  135. const cData = await fetchData(measuringPoint, apiToken, curr_dates.fromDate, curr_dates.toDate);
  136.  
  137. const prev_data = formatData(pData);
  138. const curr_data = formatData(cData);
  139.  
  140. if (curr_data === undefined) {
  141. const widget = new ListWidget();
  142. widget.addText('404 - data not found');
  143. return widget;
  144. }
  145.  
  146. if (size != 'small') {
  147. const widget = new ListWidget();
  148. widget.addText('size currently not supported');
  149. return widget;
  150. }
  151.  
  152. const widget = new ListWidget();
  153. let nextRefresh = Date.now() + 1000 * 60 * 60 * 1; // 1hour
  154. widget.refreshAfterDate = new Date(nextRefresh);
  155. widget.setPadding(10, 15, 15, 15); // top, leading, bot, trailing
  156. const gradient = new LinearGradient()
  157. gradient.locations = [0, 1]
  158. gradient.colors = [
  159. new Color('#034078'),
  160. new Color('#022A50')
  161. ]
  162. widget.backgroundGradient = gradient
  163.  
  164. const contentStack = widget.addStack();
  165. contentStack.layoutVertically();
  166.  
  167. // Main info section with large total price
  168. const primaryInfo = contentStack.addStack();
  169. primaryInfo.layoutHorizontally();
  170.  
  171. let boltImage = primaryInfo.addImage(SFSymbol.named("bolt.fill").image);
  172. boltImage.tintColor = new Color('#FFD700');
  173. boltImage.imageSize = new Size(35, 35);
  174.  
  175. primaryInfo.addSpacer();
  176.  
  177. prev_total = calculateTotal(prev_data)
  178. curr_total = calculateTotal(curr_data)
  179.  
  180. const priceInfoBox = primaryInfo.addStack();
  181. priceInfoBox.layoutVertically();
  182. priceInfoBox.centerAlignContent();
  183.  
  184. const p_priceBox = priceInfoBox.addText(`${prev_total.toFixed(2)} €`);
  185. p_priceBox.font = Font.regularRoundedSystemFont(12);
  186. p_priceBox.minimumScaleFactor = 0.75;
  187. p_priceBox.textColor = Color.white();
  188. p_priceBox.shadowColor = new Color("#000000", 0.3);
  189. p_priceBox.shadowRadius = 2;
  190. p_priceBox.shadowOffset = new Point(2, 2);
  191. p_priceBox.rightAlignText();
  192.  
  193. // priceInfoBox.addSpacer();
  194.  
  195. const priceBox = priceInfoBox.addText(`${curr_total.toFixed(2)} €`);
  196. priceBox.font = Font.boldRoundedSystemFont(20);
  197. priceBox.minimumScaleFactor = 0.75;
  198. priceBox.textColor = Color.white();
  199. priceBox.shadowColor = new Color("#000000", 0.3);
  200. priceBox.shadowRadius = 2;
  201. priceBox.shadowOffset = new Point(2, 2);
  202. priceBox.rightAlignText();
  203.  
  204. contentStack.addSpacer();
  205.  
  206. // block data
  207. for (let block_number of ['1', '2', '3', '4', '5']) {
  208. const pBlock = prev_data[block_number];
  209. const block = curr_data[block_number];
  210.  
  211. const blockStack = contentStack.addStack();
  212. blockStack.layoutHorizontally();
  213. blockStack.centerAlignContent();
  214.  
  215. const blockName = blockStack.addText(block.name);
  216. blockName.font = Font.regularRoundedSystemFont(12);
  217. blockName.textColor = Color.white();
  218. blockName.shadowColor = new Color("#000000", 0.3);
  219. blockName.shadowRadius = 2;
  220. blockName.shadowOffset = new Point(2, 2);
  221. blockName.leftAlignText();
  222.  
  223. blockStack.addSpacer();
  224.  
  225. const p_blockPrice = blockStack.addText(pBlock.priceValue ? pBlock.priceValue.toFixed(1) + ' kW' : '');
  226. p_blockPrice.font = Font.regularRoundedSystemFont(12);
  227. p_blockPrice.textColor = Color.white();
  228. p_blockPrice.shadowColor = new Color("#000000", 0.3);
  229. p_blockPrice.shadowRadius = 2;
  230. p_blockPrice.shadowOffset = new Point(2, 2);
  231. p_blockPrice.rightAlignText();
  232.  
  233. blockStack.addSpacer();
  234.  
  235. const blockPrice = blockStack.addText(block.priceValue ? block.priceValue.toFixed(1) + ' kW' : '');
  236. blockPrice.font = Font.boldRoundedSystemFont(14);
  237. blockPrice.textColor = Color.white();
  238. blockPrice.shadowColor = new Color("#000000", 0.3);
  239. blockPrice.shadowRadius = 2;
  240. blockPrice.shadowOffset = new Point(2, 2);
  241. blockPrice.rightAlignText();
  242. }
  243.  
  244. // // Refresh date
  245. // const dateStack = contentStack.addStack();
  246.  
  247. // const date = dateStack.addText(`🔄 ${resort.date}`);
  248. // date.font = Font.systemFont(10);
  249. // date.textColor = Color.white();
  250.  
  251. return widget;
  252. }
  253.  
  254. // Helper functions
  255. async function fetchData(measuringPoint, apiToken, fromDate, toDate) {
  256. // dates in format YYYY-MM-DD
  257. let url = "https://api.informatika.si/mojelektro/v1/meter-readings?"
  258. + "option=ReadingType%3D32.0.2.4.1.2.37.0.0.0.0.0.0.0.0.3.38.0"
  259. + `&usagePoint=${measuringPoint}`
  260. + `&startTime=${fromDate}&endTime=${toDate}`
  261. let req = new Request(url)
  262. req.headers = {
  263. "X-API-TOKEN": apiToken,
  264. "Content-Type": "application/json"
  265. }
  266. let jsonData = await req.loadJSON()
  267.  
  268. return jsonData.intervalBlocks[0].intervalReadings
  269. }
  270.  
  271. function calculateTotal(data) {
  272. // calculate total price
  273. let total = 0
  274. for (let block of Object.values(data)) {
  275. total += block.priceValue * block.price
  276. }
  277. total += 0.99
  278. total = total * 1.22
  279.  
  280. return total
  281. }
  282.  
  283. function formatData(readings) {
  284. // summarize readings into block data
  285. let blocks = {
  286. "1": { name: "B1", color: "#ED1D1D", price: 3.42250, priceValue: minPowerConsumption, maxValue: 0 },
  287. "2": { name: "B2", color: "#F05000", price: 0.91224, priceValue: minPowerConsumption, maxValue: 0 },
  288. "3": { name: "B3", color: "#FFB03A", price: 0.16297, priceValue: minPowerConsumption, maxValue: 0 },
  289. "4": { name: "B4", color: "#2D808B", price: 0.00407, priceValue: minPowerConsumption, maxValue: 0 },
  290. "5": { name: "B5", color: "#2C232B", price: 0.00000, priceValue: minPowerConsumption, maxValue: 0 }
  291. }
  292. for (let reading of readings) {
  293. let blockNumber = getBlockNumberForReading(reading)
  294. blocks[blockNumber].maxValue = Math.max(blocks[blockNumber].maxValue, parseFloat(reading.value))
  295. blocks[blockNumber].priceValue = Math.max(blocks[blockNumber].priceValue, parseFloat(reading.value))
  296. }
  297. // if previous block has higher max value than current block,
  298. // set current block max value to previous block max value
  299. let previousBlock = null
  300. for (let block of Object.values(blocks)) {
  301. if (previousBlock) {
  302. block.priceValue = Math.max(block.priceValue, previousBlock.priceValue)
  303. }
  304. previousBlock = block
  305. }
  306.  
  307. let month = new Date().getMonth()
  308. let isLowTariff = month >= low_tariff_months_start && month <= low_tariff_months_end;
  309.  
  310. // if low tariff is active remove block 1
  311. if (isLowTariff) {
  312. // delete blocks["1"]
  313. blocks["1"].maxValue = 0
  314. blocks["1"].priceValue = 0
  315. } else {
  316. // delete blocks["5"]
  317. blocks["5"].maxValue = 0
  318. blocks["5"].priceValue = 0
  319. }
  320.  
  321. return blocks
  322. }
  323.  
  324. function getBlockNumberForReading(reading) {
  325. let reading_time = new Date(reading.timestamp)
  326. let hour = reading_time.getHours()
  327. let date = reading_time.getDate()
  328. let month = reading_time.getMonth()
  329. let day = reading_time.getDay()
  330. let isWeekend = day == 0 || day == 6
  331. let isHoliday = holidays.some(holiday => holiday.getMonth() == month && holiday.getDate() == date)
  332. let tariff = month >= low_tariff_months_start && month <= low_tariff_months_end ? 'lt' : 'ht'
  333. let block_name = Object.keys(blocks).find(block => {
  334. let start = parseInt(block.split('_')[0].split('T')[1]);
  335. let end = parseInt(block.split('_')[1]);
  336. return hour >= start && hour < end;
  337. })
  338. return isWeekend || isHoliday ? blocks[block_name].weekends[tariff] : blocks[block_name].weekdays[tariff];
  339. }
  340.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement