Merge branch 'espruino:master' into master

master
CarlR9 2023-02-08 18:08:25 +13:00 committed by GitHub
commit 3a7f159afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 260 additions and 11 deletions

View File

@ -2,3 +2,4 @@
0.02: Update to work with Bangle.js 2
0.03: Select GNSS systems to use for Bangle.js 2
0.04: Now turns GPS off after upload
0.05: Fix regression in 0.04 that caused AGPS data not to get loaded

View File

@ -158,7 +158,7 @@
var chunk = bin.substr(i,chunkSize);
js += `\x10Serial1.write(atob("${btoa(chunk)}"))\n`;
}
js = "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
js += "\x10setTimeout(() => Bangle.setGPSPower(0,'agps'), 1000);\n"; // turn GPS off after a delay
return js;
}

View File

@ -1,7 +1,7 @@
{
"id": "assistedgps",
"name": "Assisted GPS Updater (AGPS)",
"version": "0.04",
"version": "0.05",
"description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
"sortorder": -1,
"icon": "app.png",

View File

@ -4,7 +4,7 @@ Based on the Pebble watchface Weather Land.
Mountain Pass Clock changes depending on time (day/night) and weather conditions.
This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification). To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather.
This clock requires Gadgetbridge and an app that Gadgetbridge can use to get the current weather from OpenWeatherMap (e.g. Weather Notification), or a Bangle app that will update weather.json such as OWM Weather. To set up Gadgetbridge and weather, see https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather.
The scene will change according to the following OpenWeatherMap conditions: clear, cloudy, overcast, lightning, drizzle, rain, fog and snow. Each weather condition has night/day scenes.

View File

@ -323,11 +323,28 @@ function setWeather() {
draw(a);
}
function readWeather() {
var weatherJson = require("Storage").readJSON('weather.json', 1);
// save updated weather data if available and it has been an hour since last updated
if (weatherJson !== undefined && (data.time === undefined || (data.time + 3600000) < weatherJson.weather.time)) {
data = {
time: weatherJson.weather.time,
temp: weatherJson.weather.temp,
code: weatherJson.weather.code
};
require("Storage").writeJSON('mtnclock.json', data);
}
}
const _GB = global.GB;
global.GB = (event) => {
if (event.t==="weather") {
data = event;
require("Storage").write('mtnclock.json', event);
data = {
temp: event.temp,
code: event.code,
time: Date.now()
};
require("Storage").writeJSON('mtnclock.json', data);
setWeather();
}
if (_GB) setTimeout(_GB, 0, event);
@ -340,11 +357,13 @@ function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
readWeather();
setWeather();
queueDraw();
}, 60000 - (Date.now() % 60000));
}
queueDraw();
readWeather();
setWeather();
Bangle.setUI("clock");

View File

@ -2,7 +2,7 @@
"id": "mtnclock",
"name": "Mountain Pass Clock",
"shortName": "Mtn Clock",
"version": "0.01",
"version": "0.02",
"description": "A clock that changes scenery based on time and weather.",
"readme":"README.md",
"icon": "app.png",

View File

@ -3,3 +3,4 @@
0.03: Use default Bangle formatter for booleans
0.04: Remove calibration with current voltage (Calibrate->Auto) as it is now handled by settings app
Allow automatic calibration on every charge longer than 3 hours
0.05: Add back button to settings menu.

View File

@ -2,7 +2,7 @@
"id": "powermanager",
"name": "Power Manager",
"shortName": "Power Manager",
"version": "0.04",
"version": "0.05",
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
"icon": "app.png",
"type": "bootloader",

View File

@ -23,6 +23,7 @@
'': {
'title': 'Power Manager'
},
"< Back" : back,
'Monotonic percentage': {
value: !!settings.forceMonoPercentage,
onchange: v => {

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Fix fast loading on swipe to clock
0.03: Adds a setting for going back to clock on a timeout
0.04: Fix timeouts closing fast loaded apps

View File

@ -111,6 +111,7 @@ let layout = new Layout({
remove: ()=>{
Bangle.removeListener("swipe", onSwipe);
Bangle.removeListener("touch", updateTimeout);
if (timeout) clearTimeout(timeout);
delete Graphics.prototype.setFont8x12;
}
});

View File

@ -2,7 +2,7 @@
"id": "qcenter",
"name": "Quick Center",
"shortName": "QCenter",
"version": "0.03",
"version": "0.04",
"description": "An app for quickly launching your favourite apps, inspired by the control centres of other watches.",
"icon": "app.png",
"tags": "",

223
apps/sched/interface.html Normal file
View File

@ -0,0 +1,223 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<script src="../../core/lib/interface.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/0.0.3/ical.min.js"></script>
<script>
let dataElement = document.getElementById("data");
let alarms;
let schedSettings;
function readFile(input) {
document.getElementById('upload').disabled = true;
const offsetMinutes = document.getElementById("offsetMinutes").value;
for(let i=0; i<input.files.length; i++) {
const reader = new FileReader();
reader.addEventListener("load", () => {
const jCalData = ICAL.parse(reader.result);
const comp = new ICAL.Component(jCalData[1]);
// Fetch the VEVENT part
comp.getAllSubcomponents('vevent').forEach(vevent => {
event = new ICAL.Event(vevent);
const exists = alarms.some(alarm => alarm.id === event.uid);
const alarm = eventToAlarm(event, offsetMinutes*60*1000);
renderAlarm(alarm, exists);
if (exists) {
alarms = alarms.filter(alarm => alarm.id !== event.uid); // remove if already exists
const tr = document.querySelector(`.event-row[data-uid='${event.uid}']`);
document.getElementById('events').removeChild(tr);
}
alarms.push(alarm);
});
}, false);
reader.readAsText(input.files[i], "UTF-8");
}
}
function dateToMsSinceMidnight(date) {
const dateMidnight = new Date(date);
dateMidnight.setHours(0,0,0,0);
return date - dateMidnight;
}
function dateFromAlarm(alarm) {
const date = new Date(alarm.date);
return new Date(date.getTime() + alarm.t);
}
function getAlarmDefaults() {
const date = new Date();
return {
on: true,
t: dateToMsSinceMidnight(date),
dow: 127,
date: date.toISOString().substring(0,10),
last: 0,
rp: "defaultRepeat" in schedSettings ? schedSettings.defaultRepeat : false,
vibrate: "defaultAlarmPattern" in schedSettings ? schedSettings.defaultAlarmPattern : "::",
as: false,
};
}
function eventToAlarm(event, offsetMs) {
const dateOrig = event.startDate.toJSDate();
const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig;
const alarm = {...getAlarmDefaults(), ...{
id: event.uid,
msg: event.summary,
t: dateToMsSinceMidnight(date),
date: date.toISOString().substring(0,10),
data: {end: event.endDate.toJSDate().toISOString()}
}};
if (offsetMs) { // Alarm time is not real event time, so do a backup
alarm.data.time = dateOrig.toISOString();
}
return alarm;
}
function upload() {
Util.showModal("Saving...");
Util.writeStorage("sched.json", JSON.stringify(alarms), () => {
location.reload(); // reload so we see current data
});
}
function renderAlarm(alarm, exists) {
const localDate = dateFromAlarm(alarm);
const tr = document.createElement('tr');
tr.classList.add('event-row');
tr.dataset.uid = alarm.id;
const tdTime = document.createElement('td');
tr.appendChild(tdTime);
const inputTime = document.createElement('input');
inputTime.type = "datetime-local";
inputTime.classList.add('event-date');
inputTime.classList.add('form-input');
inputTime.dataset.uid = alarm.id;
inputTime.value = localDate.toISOString().slice(0,16);
inputTime.onchange = (e => {
const date = new Date(inputTime.value);
alarm.t = dateToMsSinceMidnight(date);
alarm.date = date.toISOString().substring(0,10);
});
tdTime.appendChild(inputTime);
const tdSummary = document.createElement('td');
tr.appendChild(tdSummary);
const inputSummary = document.createElement('input');
inputSummary.type = "text";
inputSummary.classList.add('event-summary');
inputSummary.classList.add('form-input');
inputSummary.dataset.uid = alarm.id;
inputSummary.maxLength=40;
const realHumanStartTime = alarm.data?.time ? ' ' + (new Date(alarm.data.time)).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '';
const summary = (alarm.msg?.substring(0, inputSummary.maxLength) || "");
inputSummary.value = summary.endsWith(realHumanStartTime) ? summary : summary + realHumanStartTime;
inputSummary.onchange = (e => {
alarm.msg = inputSummary.value;
});
tdSummary.appendChild(inputSummary);
inputSummary.onchange();
const tdInfo = document.createElement('td');
tr.appendChild(tdInfo);
const buttonDelete = document.createElement('button');
buttonDelete.classList.add('btn');
buttonDelete.classList.add('btn-action');
tdInfo.prepend(buttonDelete);
const iconDelete = document.createElement('i');
iconDelete.classList.add('icon');
iconDelete.classList.add('icon-delete');
buttonDelete.appendChild(iconDelete);
buttonDelete.onclick = (e => {
alarms = alarms.filter(a => a !== alarm);
document.getElementById('events').removeChild(tr);
});
document.getElementById('events').appendChild(tr);
document.getElementById('upload').disabled = false;
}
function addAlarm() {
const alarm = getAlarmDefaults();
renderAlarm(alarm);
alarms.push(alarm);
}
function getData() {
Util.showModal("Loading...");
Util.readStorage('sched.json',data=>{
alarms = JSON.parse(data || "[]") || [];
Util.readStorage('sched.settings.json',data=>{
schedSettings = JSON.parse(data || "{}") || {};
Util.hideModal();
alarms.forEach(alarm => {
if (alarm.date) {
renderAlarm(alarm, true);
}
});
});
});
}
// Called when app starts
function onInit() {
getData();
}
</script>
</head>
<body>
<h4>Manage dated events</h4>
<div class="float-right">
<button class="btn" onclick="addAlarm()">
<i class="icon icon-plus"></i>
</button>
</div>
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Summary</th>
<th></th>
</tr>
</thead>
<tbody id="events">
</tbody>
</table>
<div class="divider"></div>
<div class="form-horizontal">
<div class="form-group">
<div class="col-5 col-xs-12">
<label class="form-label" for="fileinput">Add from iCalendar file</label>
</div>
<div class="col-7 col-xs-12">
<input id="fileinput" class="form-input" type="file" onchange="readFile(this)" accept=".ics,.ifb,.ical,.ifbf" multiple/>
</div>
</div>
<div class="form-group">
<div class="col-5 col-xs-12">
<label class="form-label" for="fileinput">Minutes to alarm in advance</label>
</div>
<div class="col-7 col-xs-12">
<input id="offsetMinutes" class="form-input" type="number" value="0" min="0" step="5"/>
</div>
</div>
</div>
<div class="divider"></div>
<button id="upload" class="btn btn-primary" onClick="upload()" disabled>Upload</button>
<button id="reload" class="btn" onClick="location.reload()">Reload</button>
</body>
</html>

View File

@ -10,6 +10,7 @@
"provides_modules" : ["sched"],
"default" : true,
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"sched.boot.js","url":"boot.js"},
{"name":"sched.js","url":"sched.js"},

View File

@ -4,3 +4,4 @@
Add option to show seconds
0.03: Fix Bell not appearing on alarms > 24h and redrawing interval
Update to match the default alarm widget, and not show itself when an alarm is hidden.
0.04: Fix check for active alarm

View File

@ -2,7 +2,7 @@
"id": "widalarmeta",
"name": "Alarm & Timer ETA",
"shortName": "Alarm ETA",
"version": "0.03",
"version": "0.04",
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
"icon": "widget.png",
"type": "widget",

View File

@ -9,10 +9,10 @@
function draw() {
const times = alarms
.map(alarm => {
.map(alarm =>
alarm.hidden !== true
&& require("sched").getTimeToAlarm(alarm)
})
)
.filter(a => a !== undefined);
const next = times.length > 0 ? Math.min.apply(null, times) : 0;
let calcWidth = 0;