diff --git a/apps/bad/ChangeLog b/apps/bad/ChangeLog new file mode 100644 index 000000000..263d4078d --- /dev/null +++ b/apps/bad/ChangeLog @@ -0,0 +1 @@ +0.01: attempt to import diff --git a/apps/bad/README.md b/apps/bad/README.md new file mode 100644 index 000000000..156b34cbf --- /dev/null +++ b/apps/bad/README.md @@ -0,0 +1,8 @@ +# Bad Apple demo ![](app.png) + +"Bad Apple" is like "Hello World" for graphics system. So this is it +for Bangle.js2. Watch have no speaker, so vibration motor is used, +instead, to produce (pretty quiet) sound. + +Tools for preparing bad.araw and bad.vraw are in prep/ directory. Full +3 minute demo should actually fit to the watch. \ No newline at end of file diff --git a/apps/bad/app-icon.js b/apps/bad/app-icon.js new file mode 100644 index 000000000..e153d6397 --- /dev/null +++ b/apps/bad/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIifiAFWj//Aod///gAgMH///+AFBn4FB/AQDAoYEB//8gEBAokDAoX+ApguCAAIFqGoYFNLIZHBApZxFAoyDCAoqJCSoqsBUIcPAoKtF4AFBJAS/DHIQAeA=")) diff --git a/apps/bad/app.png b/apps/bad/app.png new file mode 100644 index 000000000..e3e3dad82 Binary files /dev/null and b/apps/bad/app.png differ diff --git a/apps/bad/bad.app.js b/apps/bad/bad.app.js new file mode 100644 index 000000000..453f0003c --- /dev/null +++ b/apps/bad/bad.app.js @@ -0,0 +1,65 @@ +/* sox Rear_Right.wav -r 4k -b 8 -c 1 -e unsigned-integer 0.raw vol 2 + aplay -r 4000 /tmp/0.raw +*/ + +/* https://forum.espruino.com/conversations/348912/ */ + +let pin = D19; + +function play(name, callback) { + function playChar(offs) { + var l = 10240; + var s = require("Storage").read(name, offs, l); + //print("Waveform " + name + " " + s.length); + if (!s.length) { + digitalWrite(pin,0); + if (callback) callback(); + return; + } + var w = new Waveform(s.length); + var b = w.buffer; + b.set(s); + //print("Buffer", s.length); + //for (var i=s.length-1;i>=0;i--)b[i]/=4; + w.startOutput(pin, 4000); + w.on("finish", function(buf) { + playChar(offs+l); + }); + } + analogWrite(pin, 0.1, {freq:40000}); + playChar(0); +} + +function video(name, callback) { + function frame() { + var s = require("Storage").read(name, offs, l); + if (!s) + return; + g.drawImage(s, 0, 0, { scale: 2 }); + g.flip(); + offs += l; + } + g.clear(); + var offs = 0; + //var l = 3875; for 176x176 + //var l = 515; for 64x64 + var l = 971; + setInterval(frame, 200); +} + +function run() { + clearInterval(i); + print("Running"); + play('bad.araw'); + t1 = getTime(); + video('bad.vraw'); + print("100 frames in ", getTime()-t1); + // 1.7s, unscaled + // 2.68s, scale 1.01 + // 5.73s, scale 2.00 + // 9.93s, scale 2, full screen + // 14.4s scaled. 176/64 +} + +print("Loaded"); +i = setInterval(run, 100); \ No newline at end of file diff --git a/apps/bad/bad.araw b/apps/bad/bad.araw new file mode 100644 index 000000000..cfe5f4fdd Binary files /dev/null and b/apps/bad/bad.araw differ diff --git a/apps/bad/bad.vraw b/apps/bad/bad.vraw new file mode 100644 index 000000000..ff8fec1b0 Binary files /dev/null and b/apps/bad/bad.vraw differ diff --git a/apps/bad/metadata.json b/apps/bad/metadata.json new file mode 100644 index 000000000..882b7f066 --- /dev/null +++ b/apps/bad/metadata.json @@ -0,0 +1,16 @@ +{ "id": "bad", + "name": "Bad Apple", + "version":"0.01", + "description": "Bad Apple demo", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "allow_emulator": false, + "tags": "game", + "storage": [ + {"name":"bad.app.js","url":"bad.app.js"}, + {"name":"bad.vraw","url":"bad.vraw"}, + {"name":"bad.araw","url":"bad.araw"}, + {"name":"bad.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/bad/prep/img_convert.py b/apps/bad/prep/img_convert.py new file mode 100755 index 000000000..2edbbdef5 --- /dev/null +++ b/apps/bad/prep/img_convert.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + +from PIL import Image +import os + +def convert_image(input_path, output_width, output_height): + img = Image.open(input_path) + img_resized = img.resize((output_width, output_height), Image.ANTIALIAS) + img_gray = img_resized.convert('L') + img_1bpp = img_gray.point(lambda x: 0 if x < 128 else 255, '1') + return img_1bpp + +def convert_and_append_header(input_directory, size): + input_files = [f for f in os.listdir(input_directory) if f.startswith("image_") and f.endswith(".png")] + input_files.sort() + header_bytes = size.to_bytes(1, byteorder='big') + size.to_bytes(1, byteorder='big') + b'\x01' + + for i, input_file in enumerate(input_files): + input_path = os.path.join(input_directory, input_file) + img_1bpp = convert_image(input_path, size, size) + output_file = input_path + ".raw" + + with open(output_file, 'wb') as raw_file: + raw_file.write(header_bytes) + raw_file.write(img_1bpp.tobytes()) + +if __name__ == "__main__": + input_directory = "." # Replace with the path to your image directory + output_width = 88 + output_file_path = "output_with_header.raw" # Replace with the desired output file path + + convert_and_append_header(input_directory, output_width) diff --git a/apps/bad/prep/run b/apps/bad/prep/run new file mode 100755 index 000000000..907e711f3 --- /dev/null +++ b/apps/bad/prep/run @@ -0,0 +1,20 @@ +#!/bin/bash + +# aplay -r 4000 /tmp/0.raw +#bug: Terminal exists on b.js, it is dumb terminal, not vt100. + +rm image_*.png image_*.png.raw output.wav ../bad.araw ../bad.vraw + +I=bad.mp4 +S=1:18 +E=1:50 + +ffmpeg -i $I -ss $S -to $E -vn -acodec pcm_u8 -ar 4000 -ac 1 -y output.wav +./wav_divider.py +mv output.raw ../bad.araw + +ffmpeg -i $I -ss $S -to $E -r 5 -vf fps=5 image_%04d.png +./img_convert.py +cat *.png.raw > ../bad.vraw + +ls -al ../bad.* diff --git a/apps/bad/prep/wav_divider.py b/apps/bad/prep/wav_divider.py new file mode 100755 index 000000000..9ce08e28b --- /dev/null +++ b/apps/bad/prep/wav_divider.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 + +def divide_bytes(input_file, output_file): + with open(input_file, 'rb') as infile: + with open(output_file, 'wb') as outfile: + byte = infile.read(1) + while byte: + # Convert byte to integer, divide by 4, and write back as byte + new_byte = bytes([int.from_bytes(byte, byteorder='big') // 3]) + outfile.write(new_byte) + byte = infile.read(1) + +divide_bytes("output.wav", "output.raw") diff --git a/apps/calendar/interface.html b/apps/calendar/interface.html index 71b9a153c..8fa624a40 100644 --- a/apps/calendar/interface.html +++ b/apps/calendar/interface.html @@ -32,12 +32,14 @@ function readFile(input) { const jCalData = ICAL.parse(icalText); const comp = new ICAL.Component(jCalData); const vtz = comp.getFirstSubcomponent('vtimezone'); - const tz = new ICAL.Timezone(vtz); + const tz = vtz != null ? new ICAL.Timezone(vtz) : null; // Fetch the VEVENT part comp.getAllSubcomponents('vevent').forEach(vevent => { const event = new ICAL.Event(vevent); - event.startDate.zone = tz; + if (tz != null) { + event.startDate.zone = tz; + } holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists const holiday = eventToHoliday(event); diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index d4a5eb53b..e2c2b1faf 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -26,4 +26,5 @@ 0.26: Use widget_utils. 0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395) 0.28: Battery Vref implemented correctly. -0.29: Support fastload. \ No newline at end of file +0.29: Support fastload. +0.30: Add many new colors to the settings and allows random colors on startup if enabled. diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 89918ae87..97b0647a3 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -19,6 +19,7 @@ the "sched" app must be installed on your device. * The lower orange line indicates the battery level. * Display graphs (day or month) for steps + hrm on the second screen. * Customizable theming colors in the settings menu of the app. + * Allow random colors on startup. * Enable or disable the alarm feature. * Enable or disbale the graphs for steps + hrm. diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 858ef51dd..9923fb445 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -16,18 +16,36 @@ let settings = { themeColor3BG: "#0094FF", disableAlarms: false, disableData: false, + randomColors: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { settings[key] = saved_settings[key]; } -/* - * Colors to use - */ -let color1 = settings.themeColor3BG; -let color2 = settings.themeColor1BG; -let color3 = settings.themeColor2BG; + +//Colors to use +var color_options = [ + 'Green', 'Orange', 'Cyan', 'Purple', 'Red', 'Blue', 'Yellow', 'White', + 'Purple', 'Pink', 'Light Green', 'Brown', 'Turquoise', 'Magenta', 'Lime', + 'Gold', 'Sky Blue', 'Rose', 'Lavender', 'Amber', 'Indigo', 'Teal', + 'Crimson', 'Maroon', 'Firebrick', 'Dark Red', 'Aqua', 'Emerald', 'Royal Blue', + 'Sunset Orange', 'Turquoise Blue', 'Hot Pink', 'Goldenrod', 'Deep Sky Blue' +]; + +var bg_code = [ + '#00ff00', '#FF9900', '#0094FF', '#FF00DC', '#ff0000', '#0000ff', '#ffef00', '#FFFFFF', + '#FF00FF', '#6C00FF', '#99FF00', '#8B4513', '#40E0D0', '#FF00FF', '#00FF00', '#FFD700', + '#87CEEB', '#FF007F', '#E6E6FA', '#FFBF00', '#4B0082', '#008080', '#DC143C', '#800000', + '#B22222', '#8B0000', '#00FFFF', '#008000', '#4169E1', '#FF4500', '#40E0D0', '#FF69B4', + '#DAA520', '#00BFFF' +]; + + + +let color1; +let color2; +let color3; let cWhite = "#FFFFFF"; let cBlack = "#000000"; let cGrey = "#424242"; @@ -58,10 +76,77 @@ let convert24to16 = function(input) return "0x"+RGB565.toString(16); }; -let color1C = convert24to16(color1);//Converting colors to the correct format. -let color2C = convert24to16(color2); -let color3C = convert24to16(color3); +//Converting colors to the correct format. +let randomColors = function () { + + if (settings.randomColors) { + do { + color1 = getRandomColor(); + color2 = getRandomColor(); + color3 = getRandomColor(); + } while (!areColorsDistinct(color1, color2, color3)); + + } else { + color1 = settings.themeColor3BG; + color2 = settings.themeColor1BG; + color3 = settings.themeColor2BG; + } + + // Converting colors to the correct format. + color1C = convert24to16(color1); + color2C = convert24to16(color2); + color3C = convert24to16(color3); +}; + +// Function to get a random color from the bg_code array. +let getRandomColor = function () { + return bg_code[Math.floor(Math.random() * bg_code.length)]; +}; + +// Function to check if three colors are distinct enough. +let areColorsDistinct = function (color1, color2, color3) { + return ( + color1 !== color2 && + color2 !== color3 && + color1 !== color3 && + hasSufficientContrast(color1, color2) && + hasSufficientContrast(color2, color3) && + hasSufficientContrast(color1, color3) + ); +}; + +// Function to calculate contrast between two colors. +let hasSufficientContrast = function (color1, color2) { + const contrastThreshold = 0.10; // Adjust this threshold based on your preference. + + // Calculate the luminance values (for simplicity, assuming sRGB color space). + const luminance1 = getLuminance(color1); + const luminance2 = getLuminance(color2); + + // Calculate the contrast ratio. + const contrastRatio = (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05); + + // Check if the contrast ratio meets the threshold. + return contrastRatio >= contrastThreshold; +}; + +// Function to calculate luminance from a hex color. +let getLuminance = function (hexColor) { + const rgb = hexToRgb(hexColor); + return 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b; +}; + +// Function to convert hex color to RGB. +let hexToRgb = function (hex) { + const bigint = parseInt(hex.slice(1), 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return { r, g, b }; +}; + +randomColors();//Apply random colors if applied /* * Requirements and globals */ @@ -224,7 +309,7 @@ let drawData = function(key, y, c){ let _drawData = function(key, y, c){ - key = key.toUpperCase() + key = key.toUpperCase(); let text = key; let value = "ERR"; let should_print= true; @@ -271,7 +356,7 @@ let _drawData = function(key, y, c){ value = Math.round(data.altitude); printRow(text, value, y, c); } - }) + }); } else if(key == "CORET"){ value = locale.temp(parseInt(E.getTemperature())); @@ -374,7 +459,7 @@ let drawPosition0 = function(){ drawHorizontalBgLine(color2, batStart, batX2, 171, 5); drawHorizontalBgLine(cGrey, batX2, 172, 171, 5); for(let i=0; i+batStart<=172; i+=parseInt(batWidth/4)){ - drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8) + drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8); } // Draw Infos @@ -603,7 +688,7 @@ let getWeather = function(){ let speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934; weather.wind = Math.round(wind[1] * speedFactor); - return weather + return weather; } catch(ex) { // Return default @@ -649,7 +734,7 @@ let getAlarmMinutes = function(){ let increaseAlarm = function(){ try{ let minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; - let alarm = require('sched') + let alarm = require('sched'); alarm.setAlarm(TIMER_IDX, { timer : (minutes+5)*60*1000, }); @@ -662,7 +747,7 @@ let decreaseAlarm = function(){ let minutes = getAlarmMinutes(); minutes -= 5; - let alarm = require('sched') + let alarm = require('sched'); alarm.setAlarm(TIMER_IDX, undefined); if(minutes > 0){ @@ -773,8 +858,7 @@ Bangle.setUI({mode:"clock",remove:function() { widget_utils.cleanup(); }}); Bangle.loadWidgets(); - // Clear the screen once, at startup and draw clock g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); draw(); -} \ No newline at end of file +} diff --git a/apps/lcars/lcars.settings.js b/apps/lcars/lcars.settings.js index 81c71020f..db583741f 100644 --- a/apps/lcars/lcars.settings.js +++ b/apps/lcars/lcars.settings.js @@ -15,6 +15,7 @@ themeColor3BG: "#0094FF", disableAlarms: false, disableData: false, + randomColors: false, }; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; for (const key in saved_settings) { @@ -28,9 +29,21 @@ var dataOptions = ["Steps", "Battery", "BattVolt", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"]; var speedOptions = ["kph", "mph"]; - var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green']; - var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F']; + var color_options = [ + 'Green', 'Orange', 'Cyan', 'Purple', 'Red', 'Blue', 'Yellow', 'White', + 'Purple', 'Pink', 'Light Green', 'Brown', 'Turquoise', 'Magenta', 'Lime', + 'Gold', 'Sky Blue', 'Rose', 'Lavender', 'Amber', 'Indigo', 'Teal', + 'Crimson', 'Maroon', 'Firebrick', 'Dark Red', 'Aqua', 'Emerald', 'Royal Blue', + 'Sunset Orange', 'Turquoise Blue', 'Hot Pink', 'Goldenrod', 'Deep Sky Blue' +]; +var bg_code = [ + '#00ff00', '#FF9900', '#0094FF', '#FF00DC', '#ff0000', '#0000ff', '#ffef00', '#FFFFFF', + '#FF00FF', '#6C00FF', '#99FF00', '#8B4513', '#40E0D0', '#FF00FF', '#00FF00', '#FFD700', + '#87CEEB', '#FF007F', '#E6E6FA', '#FFBF00', '#4B0082', '#008080', '#DC143C', '#800000', + '#B22222', '#8B0000', '#00FFFF', '#008000', '#4169E1', '#FF4500', '#40E0D0', '#FF69B4', + '#DAA520', '#00BFFF' +]; E.showMenu({ '': { 'title': 'LCARS Clock' }, '< Back': back, @@ -80,7 +93,7 @@ }, 'Theme Color 1': { value: 0 | bg_code.indexOf(settings.themeColor1BG), - min: 0, max: 11, + min: 0, max: 34, format: v => color_options[v], onchange: v => { settings.themeColor1BG = bg_code[v]; @@ -89,7 +102,7 @@ }, 'Theme Color 2': { value: 0 | bg_code.indexOf(settings.themeColor2BG), - min: 0, max: 11, + min: 0, max: 34, format: v => color_options[v], onchange: v => { settings.themeColor2BG = bg_code[v]; @@ -98,7 +111,7 @@ }, 'Theme Color 3': { value: 0 | bg_code.indexOf(settings.themeColor3BG), - min: 0, max: 11, + min: 0, max: 34, format: v => color_options[v], onchange: v => { settings.themeColor3BG = bg_code[v]; @@ -121,5 +134,13 @@ save(); }, }, + 'Random colors on open': { + value: settings.randomColors, + format: () => (settings.randomColors ? 'Yes' : 'No'), + onchange: () => { + settings.randomColors = !settings.randomColors; + save(); + }, + }, }); }) diff --git a/apps/lcars/metadata.json b/apps/lcars/metadata.json index 5fbb8dcf2..60ce9c3f7 100644 --- a/apps/lcars/metadata.json +++ b/apps/lcars/metadata.json @@ -3,7 +3,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.29", + "version":"0.30", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", diff --git a/apps/sched/interface.html b/apps/sched/interface.html index 68547aa7e..62e45676b 100644 --- a/apps/sched/interface.html +++ b/apps/sched/interface.html @@ -20,7 +20,7 @@ function readFile(input) { const jCalData = ICAL.parse(icalText); const comp = new ICAL.Component(jCalData); const vtz = comp.getFirstSubcomponent('vtimezone'); - const tz = new ICAL.Timezone(vtz); + const tz = vtz != null ? new ICAL.Timezone(vtz) : null; // Fetch the VEVENT part comp.getAllSubcomponents('vevent').forEach(vevent => { @@ -73,7 +73,9 @@ function getAlarmDefaults() { } function eventToAlarm(event, tz, offsetMs) { - event.startDate.zone = tz; + if (tz != null) { + event.startDate.zone = tz; + } const dateOrig = event.startDate.toJSDate(); const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig;