diff --git a/miscellaneous/calendar content type/README.md b/miscellaneous/calendar content type/README.md new file mode 100644 index 00000000..6b9a9759 --- /dev/null +++ b/miscellaneous/calendar content type/README.md @@ -0,0 +1,15 @@ +# Google Calendar + +To use the 'clandar' content type, you need a helper script, using Google Apps Script. To use Google Apps Script to get all events for the next day and return them via JSON in a web app, you can follow these steps: + +* Create a new Google Apps Script project by going to [https://script.google.com](https://script.google.com) and clicking on "New project". +* Paste the content of the calendar.js file into the editor + This App Script calls the `getEventsForNextDay()` function to get the events in JSON format, creates a text output with the JSON content, sets the MIME type to JSON, and returns the output. + +* Deploy the web app by clicking on "Deploy > New Deployment" in the script editor +* Choose "Web app" as the deployment type +* Set 'run as' to 'me', and the access to "Anyone, even anonymous" +* click on "Deploy" +* Make sure to take note of the web app URL generated by Google. +* Test the web app by visiting the web app URL in a web browser. You should see the events for the next day in JSON format. + diff --git a/miscellaneous/calendar content type/calendar.js b/miscellaneous/calendar content type/calendar.js new file mode 100644 index 00000000..e42399d4 --- /dev/null +++ b/miscellaneous/calendar content type/calendar.js @@ -0,0 +1,38 @@ +function getEventsForNextDay(days) { + var start = new Date(); + start.setHours(0, 0, 0, 0); + var end = new Date(); + if (days == undefined) days = 1; + end.setDate(end.getDate() + parseInt(days, 10)); + var calendars = CalendarApp.getAllCalendars(); + var events = []; + for (var i = 0; i < calendars.length; i++) { + var calendar = calendars[i]; + var eventsInCalendar = calendar.getEvents(start, end); + for (var j = 0; j < eventsInCalendar.length; j++) { + var event = eventsInCalendar[j]; + events.push({ + calendar: i, + title: event.getTitle(), + start: Math.floor(event.getStartTime().getTime() / 1000), + end: Math.floor(event.getEndTime().getTime() / 1000), + isallday: event.isAllDayEvent() + }); + } + } + events.sort(function(a, b) { + return a.start - b.start; + }); + return JSON.stringify(events); +} + +function doGet(e) { + if(!e) { + e = {parameter: {days: 1}}; + } + const params = e.parameter; + var content = getEventsForNextDay(params.days); + var output = ContentService.createTextOutput(content); + output.setMimeType(ContentService.MimeType.JSON); + return output; +} diff --git a/miscellaneous/dayahead content type/README.md b/miscellaneous/dayahead content type/README.md new file mode 100644 index 00000000..ff7ad334 --- /dev/null +++ b/miscellaneous/dayahead content type/README.md @@ -0,0 +1,3 @@ +# Day Ahead Energy Prices + +Note: this script is for reference and documentation only. You don't need to deploy it yourself to use the Day Ahead content in OpenEpaperLink, as it makes use of a common install. diff --git a/miscellaneous/dayahead content type/get_dayahead.js b/miscellaneous/dayahead content type/get_dayahead.js new file mode 100644 index 00000000..d00f9b69 --- /dev/null +++ b/miscellaneous/dayahead content type/get_dayahead.js @@ -0,0 +1,189 @@ +function doGet(e) { + + var logger = Logger.log; + var country = e?.parameter?.country || 'NL'; + var cache = CacheService.getScriptCache(); + + var cachedData = cache.get("output" + country); + if (false && cachedData != null) { + logger('from cache'); + return ContentService.createTextOutput(cachedData) + .setMimeType(ContentService.MimeType.JSON); + } + + var factor = 1; + if (country.startsWith('NO')) { + // https://github.com/bkper/exchange-app + factor = cache.get("currencyNOK"); + if (factor == null) { + // factor = ExchangeApp.convert(1, 'EUR', 'NOK'); + factor = 11.4; + cache.put("currencyNOK", factor, 3600 * 24); + } + } + + if (country.startsWith('DK')) { + // https://github.com/bkper/exchange-app + factor = cache.get("currencyDKK"); + if (factor == null) { + // factor = ExchangeApp.convert(1, 'EUR', 'DKK'); + factor = 7.45; + cache.put("currencyDKK", factor, 3600 * 24); + } + } + + if (country.startsWith('SE')) { + // https://github.com/bkper/exchange-app + factor = cache.get("currencySEK"); + if (factor == null) { + // factor = ExchangeApp.convert(1, 'EUR', 'SEK'); + factor = 11.25; + cache.put("currencySEK", factor, 3600 * 24); + } + } + + var domain = lookupValue(country); + logger(country + ': ' + domain + ' currency: ' + factor); + + var xmlUrl = 'https://web-api.tp.entsoe.eu/api?documentType=A44&out_Domain=' + domain + '&in_Domain=' + domain + '&periodStart=' + getFormattedMidnightUTC(-1) + '&periodEnd=' + getFormattedMidnightUTC(2) + '&securityToken=*********'; + var cachedXml = cache.get(xmlUrl); + + if (cachedXml) { + logger('Using cached XML content.'); + var xmlContent = cachedXml; + } else { + logger('Fetching XML content from ' + xmlUrl); + var response = UrlFetchApp.fetch(xmlUrl); + var xmlContent = response.getContentText(); + cache.put(xmlUrl, xmlContent, 3600); + } + + xmlContent = xmlContent.replace(/xmlns(:[a-z0-9]+)?=["'][^"']*["']/g, ''); + xmlContent = xmlContent.replace(/<([a-z0-9]+):/g, '<').replace(/<\/([a-z0-9]+):/g, ' 0) { + var jsonData = []; + + + for (var j = 0; j < timeSeriesList.length; j++) { + var timeSeries = timeSeriesList[j]; + + var period = timeSeries.getChild('Period'); + + if (period) { + var startTime = period.getChild('timeInterval').getChildText('start'); + logger(startTime); + var points = period.getChildren('Point'); + + for (var i = 0; i < points.length; i++) { + var position = points[i].getChildText('position'); + var priceAmount = points[i].getChildText('price.amount') * factor; + + // Convert ISO date to epoch time (in seconds) + var epochTime = new Date(startTime).getTime() / 1000 + (position - 1) * 3600; + + jsonData.push({ + time: epochTime, + price: parseFloat(priceAmount) + }); + } + + } else { + logger('Error: No element found in TimeSeries ' + (j + 1)); + } + } + + if (jsonData.length > 36) { + jsonData.splice(0, jsonData.length - 36); + } + + cache.put("output" + country, JSON.stringify(jsonData), 3600); + logger('All Data: ' + JSON.stringify(jsonData)); + + return ContentService.createTextOutput(JSON.stringify(jsonData)) + .setMimeType(ContentService.MimeType.JSON); + } else { + logger('Error: No elements found in Publication_MarketDocument'); + return ContentService.createTextOutput('Error: No elements found in Publication_MarketDocument'); + } +} + +function getDescendantsByTagName(element, tagName) { + var matchingElements = []; + + function findDescendants(currentElement) { + var children = currentElement.getChildren(); + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (child.getName() === tagName) { + matchingElements.push(child); + } + findDescendants(child); + } + } + + findDescendants(element); + return matchingElements; +} + +function getFormattedMidnightUTC(offsetDays) { + var now = new Date(); // current date and time + var localMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + offsetDays, 0, 0, 0, 0); + + var utcMidnight = new Date(localMidnight.getTime()); + + var year = utcMidnight.getUTCFullYear(); + var month = ('0' + (utcMidnight.getUTCMonth() + 1)).slice(-2); // Months are zero-based + var day = ('0' + utcMidnight.getUTCDate()).slice(-2); + var hours = ('0' + utcMidnight.getUTCHours()).slice(-2); + var minutes = ('0' + utcMidnight.getUTCMinutes()).slice(-2); + + var formattedTime = year + month + day + hours + minutes; + return formattedTime; +} + +// https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas + +function lookupValue(country) { + var countryValues = { + 'AT': '10YAT-APG------L', + 'BE': '10YBE----------2', + 'CH': '10YCH-SWISSGRIDZ', + 'CZ': '10YCZ-CEPS-----N', + 'DE': '10Y1001A1001A82H', + 'DK1': '10YDK-1--------W', + 'DK2': '10YDK-2--------M', + 'EE': '10Y1001A1001A39I', + 'ES': '10YES-REE------0', + 'FI': '10YFI-1--------U', + 'FR': '10YFR-RTE------C', + 'LT': '10YLT-1001A0008Q', + 'LU': '10Y1001A1001A82H', + 'LV': '10YLV-1001A00074', + 'NL': '10YNL----------L', + 'NO1': '10YNO-1--------2', + 'NO2': '10YNO-2--------T', + 'NO3': '10YNO-3--------J', + 'NO4': '10YNO-4--------9', + 'NO5': '10Y1001A1001A48H', + 'PL': '10YPL-AREA-----S', + 'RO': '10YRO-TEL------P', + 'SE1': '10Y1001A1001A44P', + 'SE2': '10Y1001A1001A45N', + 'SE3': '10Y1001A1001A46L', + 'SE4': '10Y1001A1001A47J', + 'SI': '10YSI-ELES-----O', + 'SK': '10YSK-SEPS-----K' + }; + var value = countryValues[country] || '10YNL----------L'; + return value; +} + + + diff --git a/miscellaneous/render_bitmapfonts/Data/readme.txt b/miscellaneous/render_bitmapfonts/Data/readme.txt new file mode 100644 index 00000000..4c810bd3 --- /dev/null +++ b/miscellaneous/render_bitmapfonts/Data/readme.txt @@ -0,0 +1 @@ +Place the source truetype fonts in this folder \ No newline at end of file diff --git a/miscellaneous/render_bitmapfonts/README.md b/miscellaneous/render_bitmapfonts/README.md new file mode 100644 index 00000000..e63d1e4a --- /dev/null +++ b/miscellaneous/render_bitmapfonts/README.md @@ -0,0 +1,15 @@ +### Convert your own fonts to .vlw format + +Use Processing (https://processing.org/) along with the script in this folder, to convert Truetype fonts to .vlw bitmap fonts. +You have to select a subset of the characters to convert, by providing the unicode ranges, or by providing a list of characters. + +### Truetype font sources + +We cannot upload the source Truetype files, but these fonts are currently used: + +- twcondensed.ttf Tw Cen MT Condensed by Microsoft +- bahnschrift.ttf Bahnschrift by Microsoft +- calibrib.ttf Calibri Bold by Microsoft +- REFSAN.ttf MS Reference Sans Serif +- BellCentennialStd-Address.ttf Bell Centennial Std Address by Adobe + diff --git a/miscellaneous/render_bitmapfonts/render_bitmapfonts.pde b/miscellaneous/render_bitmapfonts/render_bitmapfonts.pde new file mode 100644 index 00000000..cfdd2cf6 --- /dev/null +++ b/miscellaneous/render_bitmapfonts/render_bitmapfonts.pde @@ -0,0 +1,129 @@ +import java.awt.Desktop; + +int curY = 0; +PFont myFont; +PrintWriter logOutput; + +void setup() { + + size(1200, 1000); + + boolean smooth = true; + boolean crisp = false; + curY = 0; + + int[] unicodeBlocks = { + 0x0021, 0x007E, //Basic Latin, 128, 128, Latin (52 characters), Common (76 characters) + 0x0080, 0x00FF, //Latin-1 Supplement, 128, 128, Latin (64 characters), Common (64 characters) + 0x0100, 0x017F, //Latin Extended-A, 128, 128, Latin + 0x0180, 0x024F, //Latin Extended-B, 208, 208, Latin + //0x0250, 0x02AF, //IPA Extensions, 96, 96, Latin + //0x02B0, 0x02FF, //Spacing Modifier Letters, 80, 80, Bopomofo (2 characters), Latin (14 characters), Common (64 characters) + //0x0030, 0x0039, //Example custom range (numbers 0-9) + //0x0041, 0x005A, //Example custom range (Upper case A-Z) + //0x0061, 0x007A, //Example custom range (Lower case a-z) + //0xF000, 0xF0FF, //weather font + }; + + int blockCount = unicodeBlocks.length; + int firstUnicode = 0; + int lastUnicode = 0; + char[] charset; + int index = 0, count = 0; + + for (int i = 0; i < blockCount; i+=2) { + firstUnicode = unicodeBlocks[i]; + lastUnicode = unicodeBlocks[i+1]; + if (lastUnicode < firstUnicode) { + delay(100); + System.err.println("ERROR: Bad Unicode range secified, last < first!"); + System.err.print("first in range = 0x" + hex(firstUnicode, 4)); + System.err.println(", last in range = 0x" + hex(lastUnicode, 4)); + while (true); + } + count += (lastUnicode - firstUnicode + 1); + } + + charset = new char[count]; + for (int i = 0; i < blockCount; i+=2) { + firstUnicode = unicodeBlocks[i]; + lastUnicode = unicodeBlocks[i+1]; + for (int code = firstUnicode; code <= lastUnicode; code++) { + charset[index] = Character.toChars(code)[0]; + index++; + } + } + + String str = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~°ÄÅÆÖØÚÜßáäåæéíöøúüýąČěľłńŘřŚŠź"; + char[] charsetbasic = str.toCharArray(); + str = "ACDEFHIJLMNOPRSTUVWZiortzÁØÚČŚŠ0123456789-"; + char[] charsetdaynames = str.toCharArray(); + str = "0123456789.°-"; + char[] charsetnumbers = str.toCharArray(); + + createAndSaveFont("twcondensed", ".ttf", charsetdaynames, 20, crisp); + + createAndSaveFont("bahnschrift", ".ttf", charsetbasic, 20, crisp); + createAndSaveFont("bahnschrift", ".ttf", charsetbasic, 30, crisp); + createAndSaveFont("bahnschrift", ".ttf", charsetnumbers, 70, crisp); + + createAndSaveFont("calibrib", ".ttf", charsetbasic, 30, crisp); + + createAndSaveFont("calibrib", ".ttf", charset, 16, crisp); + createAndSaveFont("REFSAN", ".ttf", charset, 12, crisp); + createAndSaveFont("BellCentennialStd-Address", ".ttf", charset, 10, crisp); + + try { + String path = sketchPath(); + Desktop.getDesktop().open(new File(path+"/FontFiles")); + } + catch(IOException e) { + println("Failed to create the file"); + } +} + +void createAndSaveFont(String fontName, String fontType, char charset[], int fontSize, boolean smooth) { + + int displayFontSize = fontSize; + myFont = createFont(fontName+fontType, displayFontSize, smooth, charset); + fill(0, 0, 0); + textFont(myFont); + int gapx = displayFontSize*10/8; + int gapy = displayFontSize*10/8; + int index = 0; + fill(0); + textSize(displayFontSize); + curY += gapy; + for (int y = curY; y < height-gapy; y += gapy) { + int x = 10; + curY = y; + while (x < width) { + + int unicode = charset[index]; + float cwidth = textWidth((char)unicode) + 2; + if ( (x + cwidth) > (width - gapx) ) break; + + text(new String(Character.toChars(unicode)), x, y); + + x += cwidth; + index++; + if (index >= charset.length) break; + } + if (index >= charset.length) break; + } + + PFont font; + font = createFont(fontName + fontType, fontSize, smooth, charset); + println("Created font " + fontName + str(fontSize) + ".vlw"); + try { + print("Saving to sketch FontFiles folder... "); + OutputStream output = createOutput("FontFiles/" + fontName + str(fontSize) + ".vlw"); + font.save(output); + output.close(); + println("OK!"); + delay(100); + } + catch (IOException e) { + println("Doh! Failed to create the file"); + } +}