Merge branch 'espruino:master' into add_waternet
|
@ -31,3 +31,4 @@
|
||||||
0.30: Send firmware and hardware versions on connection
|
0.30: Send firmware and hardware versions on connection
|
||||||
Allow alarm enable/disable
|
Allow alarm enable/disable
|
||||||
0.31: Implement API for activity fetching
|
0.31: Implement API for activity fetching
|
||||||
|
0.32: Added support for loyalty cards from gadgetbridge
|
||||||
|
|
|
@ -236,6 +236,11 @@
|
||||||
event.t="remove";
|
event.t="remove";
|
||||||
}
|
}
|
||||||
require("messages").pushMessage(event);
|
require("messages").pushMessage(event);
|
||||||
|
},
|
||||||
|
"cards" : function() {
|
||||||
|
// we receive all, just override what we have
|
||||||
|
if (Array.isArray(event.d))
|
||||||
|
require("Storage").writeJSON("android.cards.json", event.d);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var h = HANDLERS[event.t];
|
var h = HANDLERS[event.t];
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "android",
|
"id": "android",
|
||||||
"name": "Android Integration",
|
"name": "Android Integration",
|
||||||
"shortName": "Android",
|
"shortName": "Android",
|
||||||
"version": "0.31",
|
"version": "0.32",
|
||||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||||
|
@ -15,6 +15,6 @@
|
||||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||||
{"name":"android.boot.js","url":"boot.js"}
|
{"name":"android.boot.js","url":"boot.js"}
|
||||||
],
|
],
|
||||||
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}],
|
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
|
||||||
"sortorder": -8
|
"sortorder": -8
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Select which GNSS system you want.</label>
|
<label class="form-label">Select which GNSS system you want.</label>
|
||||||
<label class="form-radio">
|
<label class="form-radio">
|
||||||
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS
|
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS (fastest to get a fix)
|
||||||
</label>
|
</label>
|
||||||
<label class="form-radio">
|
<label class="form-radio">
|
||||||
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS
|
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
|
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
|
||||||
0.03: Bangle 2 support
|
0.03: Bangle 2 support
|
||||||
|
0.04: Increase size of ship, asteroids and fonts for better readability
|
||||||
|
0.05: improve collision detect for larger ship v astroid
|
||||||
|
0.06: added, 7 point asteroid ploygon, made ship solid, rather than outline
|
||||||
|
|
|
@ -18,6 +18,22 @@ if (process.env.HWVERSION==2) {
|
||||||
}
|
}
|
||||||
var W = g.getWidth();
|
var W = g.getWidth();
|
||||||
var H = g.getHeight();
|
var H = g.getHeight();
|
||||||
|
var SS = W/11; // ship back length
|
||||||
|
var SL = W/15; // ship side length
|
||||||
|
var AS = W/18; // asteroid radius
|
||||||
|
// radius of ship, assumed a circle inside equilateral traingle of side SS
|
||||||
|
// r = a / root 3 where a is length of equilateral triangle
|
||||||
|
var SR = SS / Math.sqrt(3);
|
||||||
|
var AST = [ // asteroid polygon as X/Y pairs
|
||||||
|
0 ,-1.5,
|
||||||
|
1 , 0,
|
||||||
|
0.5, 0,
|
||||||
|
0.5, 0.5,
|
||||||
|
0 , 1,
|
||||||
|
-1 , 0,
|
||||||
|
-1 , -1
|
||||||
|
];
|
||||||
|
|
||||||
g.clear().setFontAlign(0,-1);
|
g.clear().setFontAlign(0,-1);
|
||||||
|
|
||||||
function newAst(x,y) {
|
function newAst(x,y) {
|
||||||
|
@ -25,7 +41,7 @@ function newAst(x,y) {
|
||||||
x:x,y:y,
|
x:x,y:y,
|
||||||
vx:Math.random()-0.5,
|
vx:Math.random()-0.5,
|
||||||
vy:Math.random()-0.5,
|
vy:Math.random()-0.5,
|
||||||
rad:3+Math.random()*5
|
rad:3+Math.random()*AS
|
||||||
};
|
};
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
@ -41,8 +57,9 @@ var lastFrame;
|
||||||
|
|
||||||
function gameStop() {
|
function gameStop() {
|
||||||
running = false;
|
running = false;
|
||||||
g.clear();
|
g.setFont('Vector', W/7);
|
||||||
g.drawString("Game Over!",120,(H-6)/2);
|
g.setFontAlign(0,0);
|
||||||
|
g.drawString("Game Over", W/2, H/2);
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,12 +121,13 @@ function onFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
g.drawString(score,W-20,0);
|
g.setFont('Vector', 16);
|
||||||
|
g.drawString(score,W-20,16);
|
||||||
var rs = Math.PI*0.8;
|
var rs = Math.PI*0.8;
|
||||||
g.drawPoly([
|
g.fillPoly([
|
||||||
ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4,
|
ship.x+Math.cos(ship.r)*SS, ship.y+Math.sin(ship.r)*SS,
|
||||||
ship.x+Math.cos(ship.r+rs)*3, ship.y+Math.sin(ship.r+rs)*3,
|
ship.x+Math.cos(ship.r+rs)*SL, ship.y+Math.sin(ship.r+rs)*SL,
|
||||||
ship.x+Math.cos(ship.r-rs)*3, ship.y+Math.sin(ship.r-rs)*3,
|
ship.x+Math.cos(ship.r-rs)*SL, ship.y+Math.sin(ship.r-rs)*SL,
|
||||||
],true);
|
],true);
|
||||||
var na = [];
|
var na = [];
|
||||||
ammo.forEach(function(a) {
|
ammo.forEach(function(a) {
|
||||||
|
@ -137,7 +155,10 @@ function onFrame() {
|
||||||
ast.forEach(function(a) {
|
ast.forEach(function(a) {
|
||||||
a.x += a.vx*d;
|
a.x += a.vx*d;
|
||||||
a.y += a.vy*d;
|
a.y += a.vy*d;
|
||||||
g.drawCircle(a.x, a.y, a.rad);
|
//g.drawCircle(a.x, a.y, a.rad);
|
||||||
|
// a 7 point asteroid with rough circle radius of scale 2
|
||||||
|
g.drawPoly(g.transformVertices(AST,{x:a.x,y:a.y,scale:a.rad,rotate:t}),true);
|
||||||
|
|
||||||
if (a.x<0) a.x+=W;
|
if (a.x<0) a.x+=W;
|
||||||
if (a.y<0) a.y+=H;
|
if (a.y<0) a.y+=H;
|
||||||
if (a.x>=W) a.x-=W;
|
if (a.x>=W) a.x-=W;
|
||||||
|
@ -165,7 +186,7 @@ function onFrame() {
|
||||||
var dx = a.x-ship.x;
|
var dx = a.x-ship.x;
|
||||||
var dy = a.y-ship.y;
|
var dy = a.y-ship.y;
|
||||||
var d = Math.sqrt(dx*dx+dy*dy);
|
var d = Math.sqrt(dx*dx+dy*dy);
|
||||||
if (d < a.rad) crashed = true;
|
if (d < a.rad + SR) crashed = true;
|
||||||
});
|
});
|
||||||
ast=na;
|
ast=na;
|
||||||
if (!ast.length) {
|
if (!ast.length) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"id": "astroid",
|
"id": "astroid",
|
||||||
"name": "Asteroids!",
|
"name": "Asteroids!",
|
||||||
"version": "0.03",
|
"version": "0.06",
|
||||||
"description": "Retro asteroids game",
|
"description": "Retro asteroids game",
|
||||||
"icon": "asteroids.png",
|
"icon": "asteroids.png",
|
||||||
"screenshots": [{"url":"screenshot_asteroids.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"tags": "game",
|
"tags": "game",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
|
|
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Barcode{
|
||||||
|
constructor(data, options){
|
||||||
|
this.data = data;
|
||||||
|
this.text = options.text || data;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Barcode;
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: Simple app to display loyalty cards
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Cards
|
||||||
|
|
||||||
|
Simple app to display loyalty cards synced from Catima through GadgetBridge.
|
||||||
|
The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported.
|
||||||
|
|
||||||
|
To come back to the visualization of the card's details from the code view, simply press the button.
|
||||||
|
|
||||||
|
Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes).
|
||||||
|
|
||||||
|
### Supported codes types
|
||||||
|
|
||||||
|
* `CODE_39`
|
||||||
|
* `CODABAR`
|
||||||
|
* `QR_CODE`
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
|
||||||
|
This app is a proof of concept, many codes are too complex to be rendered by the bangle's screen or hardware (at least with the current logic), keep that in mind.
|
||||||
|
|
||||||
|
### How to sync
|
||||||
|
|
||||||
|
_WIP: we currently cannot synchronize cards, a PR is under review in GadgetBridge repo, soon we will see support on nightly builds_
|
||||||
|
|
||||||
|
You can test it by sending on your bangle a file like this:
|
||||||
|
|
||||||
|
_android.cards.json_
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "First card",
|
||||||
|
"value": "01234",
|
||||||
|
"note": "Some stuff",
|
||||||
|
"type": "CODE_39",
|
||||||
|
"balance": "15 EUR",
|
||||||
|
"expiration": "1691102081"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Second card",
|
||||||
|
"value": "Hello world",
|
||||||
|
"note": "This is a qr generated on the bangle!",
|
||||||
|
"type": "QR_CODE",
|
||||||
|
"balance": "2 P"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Credits
|
||||||
|
|
||||||
|
Barcode generation adapted from [lindell/JsBarcode](https://github.com/lindell/JsBarcode)
|
||||||
|
|
||||||
|
QR code generation adapted from [ricmoo/QRCode](https://github.com/ricmoo/QRCode)
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AYoIAjF/4v/F/4v/F/4v/FAdNAAsoADgv/F/4v/F/4vqu4AjF/4v/F/4v6poAjF/4AfFAYAGF/4v/F/4v/F/4v/F94A/AH4A/AH4A/ABo"))
|
|
@ -0,0 +1,210 @@
|
||||||
|
/* CARDS is a list of:
|
||||||
|
{id:int,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
expiration,
|
||||||
|
color,
|
||||||
|
balance,
|
||||||
|
note,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
//may make it configurable in the future
|
||||||
|
const WHITE=-1
|
||||||
|
const BLACK=0
|
||||||
|
|
||||||
|
var FILE = "android.cards.json";
|
||||||
|
|
||||||
|
var Locale = require("locale");
|
||||||
|
|
||||||
|
var fontSmall = "6x8";
|
||||||
|
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
|
||||||
|
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
|
||||||
|
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
|
||||||
|
|
||||||
|
var CARDS = require("Storage").readJSON("android.cards.json",true)||[];
|
||||||
|
var settings = require("Storage").readJSON("cards.settings.json",true)||{};
|
||||||
|
|
||||||
|
function getDate(timestamp) {
|
||||||
|
return new Date(timestamp*1000);
|
||||||
|
}
|
||||||
|
function formatDay(date) {
|
||||||
|
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
|
||||||
|
if (!settings.useToday) {
|
||||||
|
return formattedDate;
|
||||||
|
}
|
||||||
|
const today = new Date(Date.now());
|
||||||
|
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
|
||||||
|
return /*LANG*/"Today ";
|
||||||
|
else {
|
||||||
|
const tomorrow = new Date(Date.now() + 86400 * 1000);
|
||||||
|
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
|
||||||
|
return /*LANG*/"Tomorrow ";
|
||||||
|
}
|
||||||
|
return formattedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColor(intColor) {
|
||||||
|
return "#"+(0x1000000+Number(intColor)).toString(16).padStart(6,"0");
|
||||||
|
}
|
||||||
|
function isLight(color) {
|
||||||
|
var r = +("0x"+color.slice(1,3));
|
||||||
|
var g = +("0x"+color.slice(3,5));
|
||||||
|
var b = +("0x"+color.slice(5,7));
|
||||||
|
var threshold = 0x88 * 3;
|
||||||
|
return (r+g+b) > threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printSquareCode(binary, size) {
|
||||||
|
var padding = 5;
|
||||||
|
var ratio = (g.getWidth()-(2*padding))/size;
|
||||||
|
for (var y = 0; y < size; y++) {
|
||||||
|
for (var x = 0; x < size; x++) {
|
||||||
|
if (binary[x + y * size]) {
|
||||||
|
g.setColor(BLACK).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
|
||||||
|
} else {
|
||||||
|
g.setColor(WHITE).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function printLinearCode(binary) {
|
||||||
|
var yFrom = 15;
|
||||||
|
var yTo = 28;
|
||||||
|
var width = g.getWidth()/binary.length;
|
||||||
|
for(var b = 0; b < binary.length; b++){
|
||||||
|
var x = b * width;
|
||||||
|
if(binary[b] === "1"){
|
||||||
|
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||||
|
}
|
||||||
|
else if(binary[b]){
|
||||||
|
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCode(card) {
|
||||||
|
E.showScroller();
|
||||||
|
// keeping it on rising edge would come back twice..
|
||||||
|
setWatch(()=>showCard(card), BTN, {edge:"falling"});
|
||||||
|
// theme independent
|
||||||
|
g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||||
|
switch (card.type) {
|
||||||
|
case "QR_CODE": {
|
||||||
|
const getBinaryQR = require("cards.qrcode.js");
|
||||||
|
let code = getBinaryQR(card.value);
|
||||||
|
printSquareCode(code.data, code.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CODE_39": {
|
||||||
|
g.setFont("Vector:20");
|
||||||
|
g.setFontAlign(0,1).setColor(BLACK);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||||
|
const CODE39 = require("cards.code39.js");
|
||||||
|
let code = new CODE39(card.value, {});
|
||||||
|
printLinearCode(code.encode().data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CODABAR": {
|
||||||
|
g.setFont("Vector:20");
|
||||||
|
g.setFontAlign(0,1).setColor(BLACK);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||||
|
const codabar = require("cards.codabar.js");
|
||||||
|
let code = new codabar(card.value, {});
|
||||||
|
printLinearCode(code.encode().data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
g.clear(true);
|
||||||
|
g.setFont("Vector:30");
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.drawString(card.value, g.getWidth()/2, g.getHeight()/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCard(card) {
|
||||||
|
var lines = [];
|
||||||
|
var bodyFont = fontBig;
|
||||||
|
if(!card) return;
|
||||||
|
g.setFont(bodyFont);
|
||||||
|
//var lines = [];
|
||||||
|
if (card.name) lines = g.wrapString(card.name, g.getWidth()-10);
|
||||||
|
var titleCnt = lines.length;
|
||||||
|
var start = getDate(card.expiration);
|
||||||
|
var includeDay = true;
|
||||||
|
lines = lines.concat("", /*LANG*/"View code");
|
||||||
|
var valueLine = lines.length - 1;
|
||||||
|
if (card.expiration)
|
||||||
|
lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10));
|
||||||
|
if(card.balance)
|
||||||
|
lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10));
|
||||||
|
if(card.note && card.note.trim())
|
||||||
|
lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10));
|
||||||
|
lines = lines.concat("",/*LANG*/"< Back");
|
||||||
|
var titleBgColor = card.color ? getColor(card.color) : g.theme.bg2;
|
||||||
|
var titleColor = g.theme.fg2;
|
||||||
|
if (card.color)
|
||||||
|
titleColor = isLight(titleBgColor) ? BLACK : WHITE;
|
||||||
|
E.showScroller({
|
||||||
|
h : g.getFontHeight(), // height of each menu item in pixels
|
||||||
|
c : lines.length, // number of menu items
|
||||||
|
// a function to draw a menu item
|
||||||
|
draw : function(idx, r) {
|
||||||
|
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
|
||||||
|
g.setBgColor(idx<titleCnt ? titleBgColor : g.theme.bg).
|
||||||
|
setColor(idx<titleCnt ? titleColor : g.theme.fg).
|
||||||
|
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
|
||||||
|
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
|
||||||
|
}, select : function(idx) {
|
||||||
|
if (idx>=lines.length-2)
|
||||||
|
showList();
|
||||||
|
else if (idx==valueLine)
|
||||||
|
showCode(card);
|
||||||
|
},
|
||||||
|
back : () => showList()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/metafloor/bwip-js
|
||||||
|
// https://github.com/lindell/JsBarcode
|
||||||
|
|
||||||
|
function showList() {
|
||||||
|
if(CARDS.length == 0) {
|
||||||
|
E.showMessage(/*LANG*/"No cards");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
E.showScroller({
|
||||||
|
h : 52,
|
||||||
|
c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
|
||||||
|
draw : function(idx, r) {"ram"
|
||||||
|
var card = CARDS[idx];
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
|
||||||
|
if (!card) return;
|
||||||
|
var isPast = false;
|
||||||
|
var x = r.x+2, name = card.name;
|
||||||
|
var body = card.expiration ? formatDay(getDate(card.expiration)) : "";
|
||||||
|
if (card.balance) body += "\n" + card.balance;
|
||||||
|
if (name) g.setFontAlign(-1,-1).setFont(fontBig)
|
||||||
|
.setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2);
|
||||||
|
if (body) {
|
||||||
|
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
|
||||||
|
g.drawString(body, x+10,r.y+20);
|
||||||
|
}
|
||||||
|
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
|
||||||
|
if(card.color) {
|
||||||
|
g.setColor(getColor(card.color));
|
||||||
|
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select : idx => showCard(CARDS[idx]),
|
||||||
|
back : () => load()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
showList();
|
After Width: | Height: | Size: 218 B |
|
@ -0,0 +1,88 @@
|
||||||
|
// Encoding specification:
|
||||||
|
// http://www.barcodeisland.com/codabar.phtml
|
||||||
|
/*
|
||||||
|
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Barcode = require("cards.Barcode.js");
|
||||||
|
|
||||||
|
class codabar extends Barcode{
|
||||||
|
constructor(data, options){
|
||||||
|
if (/^[0-9\-\$\:\.\+\/]+$/.test(data)) {
|
||||||
|
data = "A" + data + "A";
|
||||||
|
}
|
||||||
|
|
||||||
|
super(data.toUpperCase(), options);
|
||||||
|
|
||||||
|
this.text = this.options.text || this.text.replace(/[A-D]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
valid(){
|
||||||
|
return /^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/.test(this.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(){
|
||||||
|
var result = [];
|
||||||
|
var encodings = this.getEncodings();
|
||||||
|
for(var i = 0; i < this.data.length; i++){
|
||||||
|
result.push(encodings[this.data.charAt(i)]);
|
||||||
|
// for all characters except the last, append a narrow-space ("0")
|
||||||
|
if (i !== this.data.length - 1) {
|
||||||
|
result.push("0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
text: this.text,
|
||||||
|
data: result.join('')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getEncodings(){
|
||||||
|
return {
|
||||||
|
"0": "101010011",
|
||||||
|
"1": "101011001",
|
||||||
|
"2": "101001011",
|
||||||
|
"3": "110010101",
|
||||||
|
"4": "101101001",
|
||||||
|
"5": "110101001",
|
||||||
|
"6": "100101011",
|
||||||
|
"7": "100101101",
|
||||||
|
"8": "100110101",
|
||||||
|
"9": "110100101",
|
||||||
|
"-": "101001101",
|
||||||
|
"$": "101100101",
|
||||||
|
":": "1101011011",
|
||||||
|
"/": "1101101011",
|
||||||
|
".": "1101101101",
|
||||||
|
"+": "1011011011",
|
||||||
|
"A": "1011001001",
|
||||||
|
"B": "1001001011",
|
||||||
|
"C": "1010010011",
|
||||||
|
"D": "1010011001"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = codabar
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Encoding documentation:
|
||||||
|
// https://en.wikipedia.org/wiki/Code_39#Encoding
|
||||||
|
/*
|
||||||
|
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Barcode = require("cards.Barcode.js");
|
||||||
|
|
||||||
|
class CODE39 extends Barcode {
|
||||||
|
constructor(data, options){
|
||||||
|
data = data.toUpperCase();
|
||||||
|
|
||||||
|
// Calculate mod43 checksum if enabled
|
||||||
|
if(options.mod43){
|
||||||
|
data += getCharacter(mod43checksum(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
super(data, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(){
|
||||||
|
// First character is always a *
|
||||||
|
var result = getEncoding("*");
|
||||||
|
|
||||||
|
// Take every character and add the binary representation to the result
|
||||||
|
for(let i = 0; i < this.data.length; i++){
|
||||||
|
result += getEncoding(this.data[i]) + "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last character is always a *
|
||||||
|
result += getEncoding("*");
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: result,
|
||||||
|
text: this.text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
valid(){
|
||||||
|
return /^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// All characters. The position in the array is the (checksum) value
|
||||||
|
var characters = [
|
||||||
|
"0", "1", "2", "3",
|
||||||
|
"4", "5", "6", "7",
|
||||||
|
"8", "9", "A", "B",
|
||||||
|
"C", "D", "E", "F",
|
||||||
|
"G", "H", "I", "J",
|
||||||
|
"K", "L", "M", "N",
|
||||||
|
"O", "P", "Q", "R",
|
||||||
|
"S", "T", "U", "V",
|
||||||
|
"W", "X", "Y", "Z",
|
||||||
|
"-", ".", " ", "$",
|
||||||
|
"/", "+", "%", "*"
|
||||||
|
];
|
||||||
|
|
||||||
|
// The decimal representation of the characters, is converted to the
|
||||||
|
// corresponding binary with the getEncoding function
|
||||||
|
var encodings = [
|
||||||
|
20957, 29783, 23639, 30485,
|
||||||
|
20951, 29813, 23669, 20855,
|
||||||
|
29789, 23645, 29975, 23831,
|
||||||
|
30533, 22295, 30149, 24005,
|
||||||
|
21623, 29981, 23837, 22301,
|
||||||
|
30023, 23879, 30545, 22343,
|
||||||
|
30161, 24017, 21959, 30065,
|
||||||
|
23921, 22385, 29015, 18263,
|
||||||
|
29141, 17879, 29045, 18293,
|
||||||
|
17783, 29021, 18269, 17477,
|
||||||
|
17489, 17681, 20753, 35770
|
||||||
|
];
|
||||||
|
|
||||||
|
// Get the binary representation of a character by converting the encodings
|
||||||
|
// from decimal to binary
|
||||||
|
function getEncoding(character){
|
||||||
|
return getBinary(characterValue(character));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBinary(characterValue){
|
||||||
|
return encodings[characterValue].toString(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCharacter(characterValue){
|
||||||
|
return characters[characterValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
function characterValue(character){
|
||||||
|
return characters.indexOf(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mod43checksum(data){
|
||||||
|
var checksum = 0;
|
||||||
|
for(let i = 0; i < data.length; i++){
|
||||||
|
checksum += characterValue(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
checksum = checksum % 43;
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CODE39;
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"id": "cards",
|
||||||
|
"name": "Cards",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Display loyalty cards",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
|
||||||
|
"tags": "cards",
|
||||||
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"cards.app.js","url":"app.js"},
|
||||||
|
{"name":"cards.settings.js","url":"settings.js"},
|
||||||
|
{"name":"cards.Barcode.js","url":"Barcode.js"},
|
||||||
|
{"name":"cards.qrcode.js","url":"qrcode.js"},
|
||||||
|
{"name":"cards.codabar.js","url":"codabar.js"},
|
||||||
|
{"name":"cards.code39.js","url":"code39.js"},
|
||||||
|
{"name":"cards.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"cards.settings.json"}]
|
||||||
|
}
|
|
@ -0,0 +1,705 @@
|
||||||
|
/*
|
||||||
|
* C source adapted from https://github.com/ricmoo/QRCode
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* This library is written and maintained by Richard Moore.
|
||||||
|
* Major parts were derived from Project Nayuki's library.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||||
|
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var c = E.compiledC(`
|
||||||
|
// int get_qr(int, int)
|
||||||
|
|
||||||
|
typedef signed char __int8_t;
|
||||||
|
typedef unsigned char __uint8_t;
|
||||||
|
typedef signed short int __int16_t;
|
||||||
|
typedef unsigned short int __uint16_t;
|
||||||
|
typedef signed int __int32_t;
|
||||||
|
typedef unsigned int __uint32_t;
|
||||||
|
|
||||||
|
typedef __int8_t int8_t;
|
||||||
|
typedef __int16_t int16_t;
|
||||||
|
typedef __int32_t int32_t;
|
||||||
|
typedef __uint8_t uint8_t;
|
||||||
|
typedef __uint16_t uint16_t;
|
||||||
|
typedef __uint32_t uint32_t;
|
||||||
|
|
||||||
|
typedef struct QRCode {
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t size;
|
||||||
|
uint8_t ecc;
|
||||||
|
uint8_t mode;
|
||||||
|
uint8_t mask;
|
||||||
|
uint8_t *modules;
|
||||||
|
} QRCode;
|
||||||
|
uint16_t qrcode_getBufferSize(uint8_t version);
|
||||||
|
|
||||||
|
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
|
||||||
|
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
|
||||||
|
|
||||||
|
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
|
||||||
|
|
||||||
|
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
|
||||||
|
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372},
|
||||||
|
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750},
|
||||||
|
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430},
|
||||||
|
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
|
||||||
|
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49},
|
||||||
|
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25},
|
||||||
|
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81},
|
||||||
|
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
|
||||||
|
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
|
||||||
|
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
|
||||||
|
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
|
||||||
|
};
|
||||||
|
static int max(int a, int b) {
|
||||||
|
if (a > b) { return a; }
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int abs(int value) {
|
||||||
|
if (value < 0) { return -value; }
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *memset(void *s, int c, int n) {
|
||||||
|
char *arr = (char *)s;
|
||||||
|
for (int i = 0; i<n; i++) {
|
||||||
|
arr[i] = c;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *memcpy(void *dest, const void *src, int n) {
|
||||||
|
char *arr1 = (char *)dest;
|
||||||
|
const char *arr2 = (char *)src;
|
||||||
|
for (int i = 0; i<n; i++) {
|
||||||
|
arr1[i] = arr2[i];
|
||||||
|
}
|
||||||
|
return arr1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strlen(const char *s) {
|
||||||
|
int i = 0;
|
||||||
|
while (s[i]) ++i;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int8_t getAlphanumeric(char c) {
|
||||||
|
if (c >= '0' && c <= '9') { return (c - '0'); }
|
||||||
|
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
|
||||||
|
switch (c) {
|
||||||
|
case ' ': return 36;
|
||||||
|
case '$': return 37;
|
||||||
|
case '%': return 38;
|
||||||
|
case '*': return 39;
|
||||||
|
case '+': return 40;
|
||||||
|
case '-': return 41;
|
||||||
|
case '.': return 42;
|
||||||
|
case '/': return 43;
|
||||||
|
case ':': return 44;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isAlphanumeric(const char *text, uint16_t length) {
|
||||||
|
while (length != 0) {
|
||||||
|
if (getAlphanumeric(text[--length]) == -1) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool isNumeric(const char *text, uint16_t length) {
|
||||||
|
while (length != 0) {
|
||||||
|
char c = text[--length];
|
||||||
|
if (c < '0' || c > '9') { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static char getModeBits(uint8_t version, uint8_t mode) {
|
||||||
|
unsigned int modeInfo = 0x7bbb80a;
|
||||||
|
if (version > 9) { modeInfo >>= 9; }
|
||||||
|
|
||||||
|
if (version > 26) { modeInfo >>= 9; }
|
||||||
|
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
|
||||||
|
if (result == 15) { result = 16; }
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct BitBucket {
|
||||||
|
uint32_t bitOffsetOrWidth;
|
||||||
|
uint16_t capacityBytes;
|
||||||
|
uint8_t *data;
|
||||||
|
} BitBucket;
|
||||||
|
static uint16_t bb_getGridSizeBytes(uint8_t size) {
|
||||||
|
return (((size * size) + 7) / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
|
||||||
|
return ((bits + 7) / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
|
||||||
|
bitBuffer->bitOffsetOrWidth = 0;
|
||||||
|
bitBuffer->capacityBytes = capacityBytes;
|
||||||
|
bitBuffer->data = data;
|
||||||
|
|
||||||
|
memset(data, 0, bitBuffer->capacityBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
|
||||||
|
bitGrid->bitOffsetOrWidth = size;
|
||||||
|
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
|
||||||
|
bitGrid->data = data;
|
||||||
|
|
||||||
|
memset(data, 0, bitGrid->capacityBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
|
||||||
|
uint32_t offset = bitBuffer->bitOffsetOrWidth;
|
||||||
|
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||||
|
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||||
|
}
|
||||||
|
bitBuffer->bitOffsetOrWidth = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
|
||||||
|
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||||
|
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||||
|
if (on) {
|
||||||
|
bitGrid->data[offset >> 3] |= mask;
|
||||||
|
} else {
|
||||||
|
bitGrid->data[offset >> 3] &= ~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
|
||||||
|
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||||
|
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||||
|
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
|
||||||
|
if (on ^ invert) {
|
||||||
|
bitGrid->data[offset >> 3] |= mask;
|
||||||
|
} else {
|
||||||
|
bitGrid->data[offset >> 3] &= ~mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
|
||||||
|
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||||
|
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
|
||||||
|
uint8_t size = modules->bitOffsetOrWidth;
|
||||||
|
for (uint8_t y = 0; y < size; y++) {
|
||||||
|
for (uint8_t x = 0; x < size; x++) {
|
||||||
|
if (bb_getBit(isFunction, x, y)) { continue; }
|
||||||
|
|
||||||
|
bool invert = 0;
|
||||||
|
switch (mask) {
|
||||||
|
case 0: invert = (x + y) % 2 == 0; break;
|
||||||
|
case 1: invert = y % 2 == 0; break;
|
||||||
|
case 2: invert = x % 3 == 0; break;
|
||||||
|
case 3: invert = (x + y) % 3 == 0; break;
|
||||||
|
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
||||||
|
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
||||||
|
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
||||||
|
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
||||||
|
}
|
||||||
|
bb_invertBit(modules, x, y, invert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
|
||||||
|
bb_setBit(modules, x, y, on);
|
||||||
|
bb_setBit(isFunction, x, y, true);
|
||||||
|
}
|
||||||
|
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||||
|
uint8_t size = modules->bitOffsetOrWidth;
|
||||||
|
|
||||||
|
for (int8_t i = -4; i <= 4; i++) {
|
||||||
|
for (int8_t j = -4; j <= 4; j++) {
|
||||||
|
uint8_t dist = max(abs(i), abs(j));
|
||||||
|
int16_t xx = x + j, yy = y + i;
|
||||||
|
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
|
||||||
|
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||||
|
for (int8_t i = -2; i <= 2; i++) {
|
||||||
|
for (int8_t j = -2; j <= 2; j++) {
|
||||||
|
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
|
||||||
|
|
||||||
|
uint8_t size = modules->bitOffsetOrWidth;
|
||||||
|
uint32_t data = ecc << 3 | mask;
|
||||||
|
uint32_t rem = data;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = data << 10 | rem;
|
||||||
|
data ^= 0x5412;
|
||||||
|
for (uint8_t i = 0; i <= 5; i++) {
|
||||||
|
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
|
||||||
|
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
|
||||||
|
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
|
||||||
|
|
||||||
|
for (int8_t i = 9; i < 15; i++) {
|
||||||
|
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
|
||||||
|
}
|
||||||
|
for (int8_t i = 0; i <= 7; i++) {
|
||||||
|
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int8_t i = 8; i < 15; i++) {
|
||||||
|
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFunctionModule(modules, isFunction, 8, size - 8, true);
|
||||||
|
}
|
||||||
|
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
|
||||||
|
|
||||||
|
int8_t size = modules->bitOffsetOrWidth;
|
||||||
|
if (version < 7) { return; }
|
||||||
|
uint32_t rem = version;
|
||||||
|
for (uint8_t i = 0; i < 12; i++) {
|
||||||
|
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t data = version << 12 | rem;
|
||||||
|
for (uint8_t i = 0; i < 18; i++) {
|
||||||
|
bool bit = ((data >> i) & 1) != 0;
|
||||||
|
uint8_t a = size - 11 + i % 3, b = i / 3;
|
||||||
|
setFunctionModule(modules, isFunction, a, b, bit);
|
||||||
|
setFunctionModule(modules, isFunction, b, a, bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
|
||||||
|
|
||||||
|
uint8_t size = modules->bitOffsetOrWidth;
|
||||||
|
for (uint8_t i = 0; i < size; i++) {
|
||||||
|
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
|
||||||
|
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
|
||||||
|
}
|
||||||
|
drawFinderPattern(modules, isFunction, 3, 3);
|
||||||
|
drawFinderPattern(modules, isFunction, size - 4, 3);
|
||||||
|
drawFinderPattern(modules, isFunction, 3, size - 4);
|
||||||
|
|
||||||
|
if (version > 1) {
|
||||||
|
|
||||||
|
uint8_t alignCount = version / 7 + 2;
|
||||||
|
uint8_t step;
|
||||||
|
if (version != 32) {
|
||||||
|
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2;
|
||||||
|
} else {
|
||||||
|
step = 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t alignPositionIndex = alignCount - 1;
|
||||||
|
uint8_t alignPosition[alignCount];
|
||||||
|
|
||||||
|
alignPosition[0] = 6;
|
||||||
|
|
||||||
|
uint8_t l_size = version * 4 + 17;
|
||||||
|
for (uint8_t i = 0, pos = l_size - 7; i < alignCount - 1; i++, pos -= step) {
|
||||||
|
alignPosition[alignPositionIndex--] = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < alignCount; i++) {
|
||||||
|
for (uint8_t j = 0; j < alignCount; j++) {
|
||||||
|
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawFormatBits(modules, isFunction, ecc, 0);
|
||||||
|
drawVersion(modules, isFunction, version);
|
||||||
|
}
|
||||||
|
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
|
||||||
|
|
||||||
|
uint32_t bitLength = codewords->bitOffsetOrWidth;
|
||||||
|
uint8_t *data = codewords->data;
|
||||||
|
|
||||||
|
uint8_t size = modules->bitOffsetOrWidth;
|
||||||
|
uint32_t i = 0;
|
||||||
|
for (int16_t right = size - 1; right >= 1; right -= 2) {
|
||||||
|
if (right == 6) { right = 5; }
|
||||||
|
|
||||||
|
for (uint8_t vert = 0; vert < size; vert++) {
|
||||||
|
for (int j = 0; j < 2; j++) {
|
||||||
|
uint8_t x = right - j;
|
||||||
|
bool upwards = ((right & 2) == 0) ^ (x < 6);
|
||||||
|
uint8_t y = upwards ? size - 1 - vert : vert;
|
||||||
|
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
|
||||||
|
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static uint32_t getPenaltyScore(BitBucket *modules) {
|
||||||
|
uint32_t result = 0;
|
||||||
|
|
||||||
|
uint8_t size = modules->bitOffsetOrWidth;
|
||||||
|
for (uint8_t y = 0; y < size; y++) {
|
||||||
|
|
||||||
|
bool colorX = bb_getBit(modules, 0, y);
|
||||||
|
for (uint8_t x = 1, runX = 1; x < size; x++) {
|
||||||
|
bool cx = bb_getBit(modules, x, y);
|
||||||
|
if (cx != colorX) {
|
||||||
|
colorX = cx;
|
||||||
|
runX = 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
runX++;
|
||||||
|
if (runX == 5) {
|
||||||
|
result += 3;
|
||||||
|
} else if (runX > 5) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint8_t x = 0; x < size; x++) {
|
||||||
|
bool colorY = bb_getBit(modules, x, 0);
|
||||||
|
for (uint8_t y = 1, runY = 1; y < size; y++) {
|
||||||
|
bool cy = bb_getBit(modules, x, y);
|
||||||
|
if (cy != colorY) {
|
||||||
|
colorY = cy;
|
||||||
|
runY = 1;
|
||||||
|
} else {
|
||||||
|
runY++;
|
||||||
|
if (runY == 5) {
|
||||||
|
result += 3;
|
||||||
|
} else if (runY > 5) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t black = 0;
|
||||||
|
for (uint8_t y = 0; y < size; y++) {
|
||||||
|
uint16_t bitsRow = 0, bitsCol = 0;
|
||||||
|
for (uint8_t x = 0; x < size; x++) {
|
||||||
|
bool color = bb_getBit(modules, x, y);
|
||||||
|
if (x > 0 && y > 0) {
|
||||||
|
bool colorUL = bb_getBit(modules, x - 1, y - 1);
|
||||||
|
bool colorUR = bb_getBit(modules, x, y - 1);
|
||||||
|
bool colorL = bb_getBit(modules, x - 1, y);
|
||||||
|
if (color == colorUL && color == colorUR && color == colorL) {
|
||||||
|
result += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
|
||||||
|
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
|
||||||
|
if (x >= 10) {
|
||||||
|
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
|
||||||
|
result += 40;
|
||||||
|
}
|
||||||
|
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
|
||||||
|
result += 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (color) { black++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint16_t total = size * size;
|
||||||
|
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
|
||||||
|
result += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
|
||||||
|
uint16_t z = 0;
|
||||||
|
for (int8_t i = 7; i >= 0; i--) {
|
||||||
|
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
||||||
|
z ^= ((y >> i) & 1) * x;
|
||||||
|
}
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rs_init(uint8_t degree, uint8_t *coeff) {
|
||||||
|
memset(coeff, 0, degree);
|
||||||
|
coeff[degree - 1] = 1;
|
||||||
|
uint16_t root = 1;
|
||||||
|
for (uint8_t i = 0; i < degree; i++) {
|
||||||
|
|
||||||
|
for (uint8_t j = 0; j < degree; j++) {
|
||||||
|
coeff[j] = rs_multiply(coeff[j], root);
|
||||||
|
if (j + 1 < degree) {
|
||||||
|
coeff[j] ^= coeff[j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root = (root << 1) ^ ((root >> 7) * 0x11D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, const uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
|
||||||
|
for (uint8_t i = 0; i < length; i++) {
|
||||||
|
uint8_t factor = data[i] ^ result[0];
|
||||||
|
for (uint8_t j = 1; j < degree; j++) {
|
||||||
|
result[(j - 1) * stride] = result[j * stride];
|
||||||
|
}
|
||||||
|
result[(degree - 1) * stride] = 0;
|
||||||
|
|
||||||
|
for (uint8_t j = 0; j < degree; j++) {
|
||||||
|
result[j * stride] ^= rs_multiply(coeff[j], factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
|
||||||
|
int8_t mode = 2;
|
||||||
|
|
||||||
|
if (isNumeric((char*)text, length)) {
|
||||||
|
mode = 0;
|
||||||
|
bb_appendBits(dataCodewords, 1 << 0, 4);
|
||||||
|
bb_appendBits(dataCodewords, length, getModeBits(version, 0));
|
||||||
|
|
||||||
|
uint16_t accumData = 0;
|
||||||
|
uint8_t accumCount = 0;
|
||||||
|
for (uint16_t i = 0; i < length; i++) {
|
||||||
|
accumData = accumData * 10 + ((char)(text[i]) - '0');
|
||||||
|
accumCount++;
|
||||||
|
if (accumCount == 3) {
|
||||||
|
bb_appendBits(dataCodewords, accumData, 10);
|
||||||
|
accumData = 0;
|
||||||
|
accumCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (accumCount > 0) {
|
||||||
|
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (isAlphanumeric((char*)text, length)) {
|
||||||
|
mode = 1;
|
||||||
|
bb_appendBits(dataCodewords, 1 << 1, 4);
|
||||||
|
bb_appendBits(dataCodewords, length, getModeBits(version, 1));
|
||||||
|
|
||||||
|
uint16_t accumData = 0;
|
||||||
|
uint8_t accumCount = 0;
|
||||||
|
for (uint16_t i = 0; i < length; i++) {
|
||||||
|
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
|
||||||
|
accumCount++;
|
||||||
|
if (accumCount == 2) {
|
||||||
|
bb_appendBits(dataCodewords, accumData, 11);
|
||||||
|
accumData = 0;
|
||||||
|
accumCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (accumCount > 0) {
|
||||||
|
bb_appendBits(dataCodewords, accumData, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bb_appendBits(dataCodewords, 1 << 2, 4);
|
||||||
|
bb_appendBits(dataCodewords, length, getModeBits(version, 2));
|
||||||
|
for (uint16_t i = 0; i < length; i++) {
|
||||||
|
bb_appendBits(dataCodewords, (char)(text[i]), 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
|
||||||
|
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
|
||||||
|
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
|
||||||
|
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||||
|
uint8_t blockEccLen = totalEcc / numBlocks;
|
||||||
|
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
|
||||||
|
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
|
||||||
|
|
||||||
|
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
|
||||||
|
|
||||||
|
uint8_t result[data->capacityBytes];
|
||||||
|
memset(result, 0, sizeof(result));
|
||||||
|
|
||||||
|
uint8_t coeff[blockEccLen];
|
||||||
|
rs_init(blockEccLen, coeff);
|
||||||
|
|
||||||
|
uint16_t offset = 0;
|
||||||
|
uint8_t *dataBytes = data->data;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
|
||||||
|
uint16_t index = i;
|
||||||
|
uint8_t stride = shortDataBlockLen;
|
||||||
|
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||||
|
result[offset++] = dataBytes[index];
|
||||||
|
if (blockNum == numShortBlocks) { stride++; }
|
||||||
|
|
||||||
|
index += stride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
|
||||||
|
uint8_t stride = shortDataBlockLen;
|
||||||
|
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
|
||||||
|
result[offset++] = dataBytes[index];
|
||||||
|
|
||||||
|
if (blockNum == 0) { stride++; }
|
||||||
|
index += stride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t blockSize = shortDataBlockLen;
|
||||||
|
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||||
|
if (blockNum == numShortBlocks) { blockSize++; }
|
||||||
|
|
||||||
|
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
|
||||||
|
dataBytes += blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data->data, result, data->capacityBytes);
|
||||||
|
data->bitOffsetOrWidth = moduleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
|
||||||
|
|
||||||
|
uint16_t qrcode_getBufferSize(uint8_t version) {
|
||||||
|
return bb_getGridSizeBytes(4 * version + 17);
|
||||||
|
}
|
||||||
|
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
|
||||||
|
uint8_t size = version * 4 + 17;
|
||||||
|
qrcode->version = version;
|
||||||
|
qrcode->size = size;
|
||||||
|
qrcode->ecc = ecc;
|
||||||
|
qrcode->modules = modules;
|
||||||
|
|
||||||
|
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
|
||||||
|
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||||
|
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
|
||||||
|
struct BitBucket codewords;
|
||||||
|
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
|
||||||
|
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
|
||||||
|
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
|
||||||
|
|
||||||
|
if (mode < 0) { return -1; }
|
||||||
|
qrcode->mode = mode;
|
||||||
|
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
|
||||||
|
if (padding > 4) { padding = 4; }
|
||||||
|
bb_appendBits(&codewords, 0, padding);
|
||||||
|
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
|
||||||
|
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
|
||||||
|
bb_appendBits(&codewords, padByte, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
BitBucket modulesGrid;
|
||||||
|
bb_initGrid(&modulesGrid, modules, size);
|
||||||
|
|
||||||
|
BitBucket isFunctionGrid;
|
||||||
|
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
|
||||||
|
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
|
||||||
|
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
|
||||||
|
performErrorCorrection(version, eccFormatBits, &codewords);
|
||||||
|
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
|
||||||
|
uint8_t mask = 0;
|
||||||
|
int32_t minPenalty = (2147483647);
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
|
||||||
|
applyMask(&modulesGrid, &isFunctionGrid, i);
|
||||||
|
int penalty = getPenaltyScore(&modulesGrid);
|
||||||
|
if (penalty < minPenalty) {
|
||||||
|
mask = i;
|
||||||
|
minPenalty = penalty;
|
||||||
|
}
|
||||||
|
applyMask(&modulesGrid, &isFunctionGrid, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
qrcode->mask = mask;
|
||||||
|
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
|
||||||
|
applyMask(&modulesGrid, &isFunctionGrid, mask);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
|
||||||
|
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
|
||||||
|
if (x >= qrcode->size || y >= qrcode->size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t offset = y * qrcode->size + x;
|
||||||
|
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_qr (char *string, uint8_t *qrcodeBitmap) {
|
||||||
|
// The structure to manage the QR code
|
||||||
|
QRCode qrcode;
|
||||||
|
|
||||||
|
// Allocate a chunk of memory to store the QR code
|
||||||
|
uint8_t qrcodeBytes[qrcode_getBufferSize(3)];
|
||||||
|
|
||||||
|
qrcode_initText(&qrcode, qrcodeBytes, 3, 0, string);
|
||||||
|
for (uint8_t y = 0; y < qrcode.size; y++) {
|
||||||
|
for (uint8_t x = 0; x < qrcode.size; x++) {
|
||||||
|
qrcodeBitmap[x + y * qrcode.size] = qrcode_getModule(&qrcode, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qrcode.size;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
function getBinaryQR (value) {
|
||||||
|
var qrcodeBitmap = new Uint8Array(850);
|
||||||
|
var flatValue = Uint8Array(E.toArrayBuffer(E.toFlatString(value ,0)));
|
||||||
|
var valueAddr = E.getAddressOf(flatValue, true);
|
||||||
|
var qrAddr = E.getAddressOf(qrcodeBitmap, true);
|
||||||
|
if (valueAddr == 0 || qrAddr == 0) {
|
||||||
|
console.log ("Failed to get flat arrays..");
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
var qrsize = c.get_qr(valueAddr, qrAddr);
|
||||||
|
return { data: qrcodeBitmap, size: qrsize };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getBinaryQR;
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
(function(back) {
|
||||||
|
var settings = require("Storage").readJSON("cards.settings.json",1)||{};
|
||||||
|
function updateSettings() {
|
||||||
|
require("Storage").writeJSON("cards.settings.json", settings);
|
||||||
|
}
|
||||||
|
var mainmenu = {
|
||||||
|
"" : { "title" : "Cards" },
|
||||||
|
"< Back" : back,
|
||||||
|
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
|
||||||
|
/*LANG*/"Use 'Today',..." : {
|
||||||
|
value : !!settings.useToday,
|
||||||
|
onchange: v => {
|
||||||
|
settings.useToday = v;
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
E.showMenu(mainmenu);
|
||||||
|
})
|
|
@ -100,7 +100,8 @@ function onInit(device) {
|
||||||
if (crc==2560806221) version = "2v15";
|
if (crc==2560806221) version = "2v15";
|
||||||
if (crc==2886730689) version = "2v16";
|
if (crc==2886730689) version = "2v16";
|
||||||
if (crc==156320890) version = "2v17";
|
if (crc==156320890) version = "2v17";
|
||||||
if (crc==4012421318) version = "2v18";
|
if (crc==4012421318) version = "2v18";
|
||||||
|
if (crc==1856454048) version = "2v19";
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
version += `(⚠ update required)`;
|
version += `(⚠ update required)`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ It provides the following features :
|
||||||
- display a local map around you, downloaded from openstreetmap
|
- display a local map around you, downloaded from openstreetmap
|
||||||
- detects and buzzes if you leave the path
|
- detects and buzzes if you leave the path
|
||||||
- (optional) buzzes before sharp turns
|
- (optional) buzzes before sharp turns
|
||||||
- (optional) buzzes before waypoints
|
- (optional) buzzes before waypoints
|
||||||
(for example when you need to turn in https://mapstogpx.com/)
|
(for example when you need to turn in https://mapstogpx.com/)
|
||||||
- display instant / average speed
|
- display instant / average speed
|
||||||
- display distance to next point
|
- display distance to next point
|
||||||
|
@ -114,7 +114,7 @@ slopes between path points. Don't expect to see small bumps on the road.
|
||||||
Few settings for now (feel free to suggest me more) :
|
Few settings for now (feel free to suggest me more) :
|
||||||
|
|
||||||
- buzz on turns : should the watch buzz when reaching a waypoint ?
|
- buzz on turns : should the watch buzz when reaching a waypoint ?
|
||||||
- disable bluetooth : turn bluetooth off completely to try to save some power.
|
- disable bluetooth : turn bluetooth off completely to try to save some power.
|
||||||
- lost distance : at which distance from path are you considered to be lost ?
|
- lost distance : at which distance from path are you considered to be lost ?
|
||||||
- wake-up speed : if you drive below this speed powersaving will disable itself
|
- wake-up speed : if you drive below this speed powersaving will disable itself
|
||||||
- active-time : how long (in seconds) the screen should be turned on if activated before going back to sleep.
|
- active-time : how long (in seconds) the screen should be turned on if activated before going back to sleep.
|
||||||
|
@ -133,7 +133,7 @@ There are now two display modes :
|
||||||
|
|
||||||
The algorithm works in the following ways :
|
The algorithm works in the following ways :
|
||||||
|
|
||||||
- some events will *activate* : the display will turn *active*
|
- some events will *activate* : the display will turn *active*
|
||||||
- if no activation event occur for at least 10 seconds (or *active-time* setting) we switch back to *inactive*
|
- if no activation event occur for at least 10 seconds (or *active-time* setting) we switch back to *inactive*
|
||||||
|
|
||||||
Activation events are the following :
|
Activation events are the following :
|
||||||
|
@ -167,8 +167,8 @@ I had to go back uphill by quite a distance.
|
||||||
|
|
||||||
Feel free to give me feedback : is it useful for you ? what other features would you like ?
|
Feel free to give me feedback : is it useful for you ? what other features would you like ?
|
||||||
|
|
||||||
If you want to raise issues the main repository is [https://github.com/wagnerf42/BangleApps](here) and
|
If you want to raise issues the main repository is [here](https://github.com/wagnerf42/BangleApps) and
|
||||||
the rust code doing the actual map computations is located [https://github.com/wagnerf42/gps](here).
|
the rust code doing the actual map computations is located [here](https://github.com/wagnerf42/gps).
|
||||||
You can try the cutting edge version at [https://wagnerf42.github.io/BangleApps/](https://wagnerf42.github.io/BangleApps/)
|
You can try the cutting edge version at [https://wagnerf42.github.io/BangleApps/](https://wagnerf42.github.io/BangleApps/)
|
||||||
|
|
||||||
frederic.wagner@imag.fr
|
frederic.wagner@imag.fr
|
||||||
|
|
|
@ -23,17 +23,7 @@ function saveKML(track,title) {
|
||||||
</Placemark>
|
</Placemark>
|
||||||
</Document>
|
</Document>
|
||||||
</kml>`;
|
</kml>`;
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(title+".kml", "application/vnd.google-earth.kml+xml", kml);
|
||||||
file = new Blob([kml], {type: "application/vnd.google-earth.kml+xml"});
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = title+".kml";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function() {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveGPX(track, title) {
|
function saveGPX(track, title) {
|
||||||
|
@ -56,17 +46,7 @@ function saveGPX(track, title) {
|
||||||
</trkseg>
|
</trkseg>
|
||||||
</trk>
|
</trk>
|
||||||
</gpx>`;
|
</gpx>`;
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(title+".gpx", "application/gpx+xml", gpx);
|
||||||
file = new Blob([gpx], {type: "application/gpx+xml"});
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = title+".gpx";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function() {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function trackLineToObject(l, hasTrackNumber) {
|
function trackLineToObject(l, hasTrackNumber) {
|
||||||
|
|
|
@ -28,6 +28,10 @@ The CSV data contains the following columns:
|
||||||
* PPG_o - `e.vcPPGoffs` from the `Bangle.on("HRM-raw"` event. This is the PPG offset used to map `e.vcPPG` to `e.raw` so there are no glitches when the exposure values in the sensor change.
|
* PPG_o - `e.vcPPGoffs` from the `Bangle.on("HRM-raw"` event. This is the PPG offset used to map `e.vcPPG` to `e.raw` so there are no glitches when the exposure values in the sensor change.
|
||||||
* BTHRM - BPM figure from external Bluetooth HRM device (this is our reference BPM)
|
* BTHRM - BPM figure from external Bluetooth HRM device (this is our reference BPM)
|
||||||
|
|
||||||
|
## FIXME
|
||||||
|
|
||||||
|
The `custom.html` for the app uses the Puck.js lib directly when it should just use `customize.js` - it won't work well under Gadgetbridge and may fail on other platforms too
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
[halemmerich](https://github.com/halemmerich)
|
[halemmerich](https://github.com/halemmerich)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script src="../../core/lib/customize.js"></script>
|
||||||
<script src="https://www.puck-js.com/puck.js"></script>
|
<script src="https://www.puck-js.com/puck.js"></script>
|
||||||
<p>
|
<p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -23,20 +24,6 @@
|
||||||
<p id="result"></p>
|
<p id="result"></p>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function saveCSV(filename, csvData) {
|
|
||||||
let a = document.createElement("a"),
|
|
||||||
file = new Blob([csvData], {type: "Comma-separated value file"});
|
|
||||||
let url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename+".csv";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function() {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCode(){
|
function createCode(){
|
||||||
//modes: 1 BT, 2 File
|
//modes: 1 BT, 2 File
|
||||||
return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw`
|
return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw`
|
||||||
|
@ -48,7 +35,7 @@ function createCode(){
|
||||||
var gotBTHRM = false;
|
var gotBTHRM = false;
|
||||||
var gotHRM = false;
|
var gotHRM = false;
|
||||||
var gotAcc = false;
|
var gotAcc = false;
|
||||||
|
|
||||||
var events = -1;
|
var events = -1;
|
||||||
var hrmRaw,hrmPulse,bthrmPulse
|
var hrmRaw,hrmPulse,bthrmPulse
|
||||||
|
|
||||||
|
@ -59,9 +46,9 @@ function createCode(){
|
||||||
let bthrmSettings = (require("Storage").readJSON("bthrm.json",1) || {});
|
let bthrmSettings = (require("Storage").readJSON("bthrm.json",1) || {});
|
||||||
|
|
||||||
Bangle.setHRMPower(1);
|
Bangle.setHRMPower(1);
|
||||||
|
|
||||||
if (bthrmSettings.replace) Bangle.origSetHRMPower(1);
|
if (bthrmSettings.replace) Bangle.origSetHRMPower(1);
|
||||||
|
|
||||||
if (Bangle.setBTHRMPower){
|
if (Bangle.setBTHRMPower){
|
||||||
Bangle.setBTHRMPower(1);
|
Bangle.setBTHRMPower(1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -166,7 +153,7 @@ function createCode(){
|
||||||
drawStatus(free>0.25*process.env.STORAGE, h++, Math.floor(free/1024) + "K");
|
drawStatus(free>0.25*process.env.STORAGE, h++, Math.floor(free/1024) + "K");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var intervalId = -1;
|
var intervalId = -1;
|
||||||
|
|
||||||
g.setFont12x20();
|
g.setFont12x20();
|
||||||
|
@ -181,7 +168,7 @@ function createCode(){
|
||||||
drawStatusText("Events", h++);
|
drawStatusText("Events", h++);
|
||||||
if (method == 2) drawStatusText("Storage", h++);
|
if (method == 2) drawStatusText("Storage", h++);
|
||||||
updateStatus();
|
updateStatus();
|
||||||
|
|
||||||
intervalId = setInterval(()=>{
|
intervalId = setInterval(()=>{
|
||||||
updateStatus();
|
updateStatus();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -221,8 +208,8 @@ document.getElementById("chkLocal").addEventListener("click", function() {
|
||||||
|
|
||||||
window.addEventListener("message", function(event) {
|
window.addEventListener("message", function(event) {
|
||||||
let msg = event.data;
|
let msg = event.data;
|
||||||
if (msg.type=="readstoragefilersp") {
|
if (msg.type=="readstoragefilersp") {
|
||||||
saveCSV("log.csv", msg.data);
|
Util.saveCSV("log", msg.data);
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
@ -240,7 +227,7 @@ document.getElementById("btnDownload").addEventListener("click", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("btnSave").addEventListener("click", function() {
|
document.getElementById("btnSave").addEventListener("click", function() {
|
||||||
saveCSV("log.csv", localStorage.getItem("data"));
|
Util.saveCSV("log", localStorage.getItem("data"));
|
||||||
});
|
});
|
||||||
|
|
||||||
function reset(){
|
function reset(){
|
||||||
|
@ -289,7 +276,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
|
||||||
l.forEach(onLine);
|
l.forEach(onLine);
|
||||||
});
|
});
|
||||||
connection.write("reset();\n", function() {
|
connection.write("reset();\n", function() {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
connection.write("\x03\x10if(1){"+createCode()+"}\n",
|
connection.write("\x03\x10if(1){"+createCode()+"}\n",
|
||||||
function() { console.log("Ready..."); });
|
function() { console.log("Ready..."); });
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1,19 @@
|
||||||
|
# LCD Clock Plus
|
||||||
|
|
||||||
|
A Casio-style clock, with four ClockInfo areas at the top and bottom. Tap them and swipe up/down and left/right to toggle between different information.
|
||||||
|
|
||||||
|
Forked from [LCD Clock](https://banglejs.com/apps/?id=lcdclock), inspired by [Cassio Watch](https://banglejs.com/apps/?id=cassioWatch).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Recommended to be used together with the [Casio Logo Widget](https://banglejs.com/apps/?id=widcasiologo).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Differences from the original LCD Clock:
|
||||||
|
|
||||||
|
* Two extra (4 total) ClockInfo areas at the bottom of the screen. ClockInfo areas are now smaller and only include the icon and the data.
|
||||||
|
* AM/PM labels moved to the right of the time and are only visible when 12-hour mode is enabled in the system locale settings.
|
||||||
|
* Month name label added.
|
||||||
|
* Background color changed to better mimic old-style LCD screens.
|
||||||
|
* Various small alignment ajustments.
|
|
@ -0,0 +1 @@
|
||||||
|
atob("MDCI/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5OTk5OUBAQEBAQEBAQEBAQEBAQEBAQDlAOTlAQEBAQEBAQEBAQEBAQEBAQEBABxU5BwAAB0BHR0dHR0dHR0dHR0dHR0dHR0BAOTJHR0dHR0dHR0dHR0dHR0dHR0dHRzkAAAAAAABHR0dAOUBHR0dAQDlAR0dHRzk5OTI5MjJHR0dAQEBHQEA5OUBAR0BHRwcAAAAAAABHR0dHRzJHR0A5DkdHR0dHRxU5OTlAQDJHR0cOQEBHOTlHQEAyRzlHRwcAAAAAAABHR0c5MkdHR0dAQEdHR0dHRzlAOTk5OUBHR0dAQDIyQEAyOUdAMjJHRw4AAAAAAABHR0A5R0dHR0A5DkdHR0dHRzk5OTk5DkdHR0cOQEBHOTlHR0cyRzlHR0AHAAAAAA5HR0c5DkdHR0dHQA45R0dHR0BHRzkVDkdHR0dAR0dHQEBHR0dAR0BHR0BHOTk5QEdHR0dHR0dHR0dHR0dHR0dHR0BHR0dAQEdHR0dHR0dHR0dHR0dHR0dHRw4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODkBHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BAOUBAR0dAQDlAR0dHR0dHR0dHR0dHRzI5R0c5OUdAOTlAR0dHR0dAR0A5OUdHR0BHRw45QEA5MkdHR0dHR0dHR0dHR0dHRzJHR0A5R0c5QEA5R0dHR0cOR0dHMkdHR0BHR0BAR0dAQA45R0dHR0dHR0dHR0dHR0AOQEcODkdADjlHR0dHR0dARzkOQEdHR0BHRw45QEA5MkdHR0dHR0dHR0dHR0dHR0dHMkA5R0c5QEdHR0dHR0cORzJHR0dHR0BHR0BHOTlHQDI5R0dHR0dHR0dHR0dHR0AyQEc5MkdHR0dHR0dHR0dARzkyR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHQAAAAABHR0dHR0dHR0AAAAAAQEdHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHOTk5MkdHR0cHQEdHR0dHR0dHR0dHB0BHR0dHR0AHR0dHR0dHR0BHR0dHR0dHR0dHDjI5K0dHR0cAQEcyBzlHR0dHR0dHB0BHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjI5K0dHR0cAQEcOADJHR0dHR0dHB0BHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjI5K0dHR0cAQEdAOUBHR0dHR0dHB0BHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHQEBAOTIyMjI5R0dHR0dHR0cyMjIyOUBHR0dHR0c5R0dHR0dHR0BHR0dHR0dHR0dHQEBHQA4ODg45R0dHR0dHQDkODg4OR0dHR0dHR0c5R0dHR0dHR0BHR0dHR0dHR0dHDjJHR0dHR0cAQEdAOUBHQAdHR0dHR0dHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjJHR0dHR0cAQEcOADJHQAdHR0dHR0dHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjJHR0dHR0cAQEcOBzlHQAdHR0dHR0dHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHFTlHR0dHR0cHQEdHR0dHQAdHR0dHR0dHR0dHR0AHR0dHR0dHR0BHR0dHR0dHR0dHR0dHQAAAAABHR0dHR0dHR0AAAAAAQEdHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHRzlAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBVAOUBAQEBAQEBAQEBAQEBAQEBAQEBAQBVAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEA5AEBHR0dHR0dHR0dHR0dHR0dHR0dHR0BHRzlAR0dHR0dHR0dHR0dHR0dHR0dHR0A5ABVAOUdHQDk5Rzk5QEc5OUBHOTlHR0BHRzlAR0dHR0BAOTlAR0dHOTlARzk5R0A5ADkOAEBHR0cOR0dHOUdHQEBHRzlHR0BHR0BHR0dHRw45QEA5Rw5HR0A5R0c5QEBHDjkHAEdHQA5AR0dHQEdHQEBHDg5HR0BAQEBHOUdHR0BHOQ5AR0BAMhVHQA4VR0BHFRU5DkdHR0cOR0dHOUdHQEBHRzlHR0BHQAAHR0dHRw5HR0A5RxU5QEdHMkdHR0BHR0cAOUdHQDJAR0dHQEdHR0dHMjlHR0A5MjIyMkBHR0BHOTlHR0dHOTlHQDI5RzlHR0dAR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHRwAyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMg4yMjIyMjIyMjIyMjIyMjIyMjIyMjIyBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
|
|
@ -0,0 +1,108 @@
|
||||||
|
Graphics.prototype.setFont7Seg = function() {
|
||||||
|
return this.setFontCustom(atob("AAAAAAAAAAAACAQCAAAAAAIAd0BgMBdwAAAAAAAADuAAAB0RiMRcAAAAAiMRiLuAAAcAQCAQdwAADgiMRiIOAAAd0RiMRBwAAAAgEAgDuAAAd0RiMRdwAADgiMRiLuAAAABsAAAd0QiEQdwAADuCIRCIOAAAd0BgMBAAAAAOCIRCLuAAAd0RiMRAAAADuiEQiAAAAAd0BgMBBwAADuCAQCDuAAAdwAAAAAAAAAAAIBALuAAAdwQCAQdwAADuAIBAIAAAAd0AgEAcEAgEAdwAd0AgEAdwAADugMBgLuAAAd0QiEQcAAADgiEQiDuAAAd0AgEAAAAADgiMRiIOAAAAEAgEAdwAADuAIBALuAAAdwBAIBdwAADuAIBAIOAIBALuADuCAQCDuAAAcAQCAQdwAAAOiMRiLgAAAA=="), 32, atob("BwAAAAAAAAAAAAAAAAcCAAcHBwcHBwcHBwcEAAAAAAAABwcHBwcHBwcHBwcHCgcHBwcHBwcHBwoHBwc="), 9);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
|
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||||
|
let drawTimeout;
|
||||||
|
|
||||||
|
require("Font7x11Numeric7Seg").add(Graphics);
|
||||||
|
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||||
|
|
||||||
|
// Actually draw the watch face
|
||||||
|
let draw = function() {
|
||||||
|
var x = R.x + R.w/2;
|
||||||
|
var y = R.y + 48;
|
||||||
|
g.reset().setColor(g.theme.bg).setBgColor(g.theme.fg);
|
||||||
|
g.clearRect(R.x,bar1Y+2,R.x2,bar2Y-2);
|
||||||
|
var date = new Date();
|
||||||
|
var timeStr = require("locale").time(date, 1); // Hour and minute
|
||||||
|
|
||||||
|
// Time
|
||||||
|
if (is12Hour) {
|
||||||
|
g.setFontAlign(-1, 0).setFont("7x11Numeric7Seg:4").drawString(timeStr, R.x, y+39);
|
||||||
|
g.setFontAlign(1, 0).setFont("7Seg:2").drawString(require("locale").meridian(date).toUpperCase(), R.x2, y+30+(date.getHours() >= 12 ? 20 : 0));
|
||||||
|
} else {
|
||||||
|
g.setFontAlign(0, 0).setFont("7x11Numeric7Seg:4").drawString(timeStr, x, y+39);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Day of week
|
||||||
|
g.setFontAlign(-1, 0).setFont("7Seg:2").drawString(require("locale").dow(date, 1).toUpperCase(), R.x+2, y);
|
||||||
|
|
||||||
|
// Date
|
||||||
|
g.setFontAlign(-1, 0).setFont("7Seg:2").drawString(require("locale").month(new Date(), 2).toUpperCase(), x, y);
|
||||||
|
g.setFontAlign(1, 0).setFont("7Seg:2").drawString(date.getDate(), R.x2 - 6, y);
|
||||||
|
|
||||||
|
// queue next draw
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
};
|
||||||
|
|
||||||
|
let clockInfoDraw = (itm, info, options) => {
|
||||||
|
let texty = options.y+41;
|
||||||
|
g.reset().setFont("7Seg").setColor(g.theme.bg).setBgColor(g.theme.fg);
|
||||||
|
if (options.focus) g.setBgColor("#FF0");
|
||||||
|
g.clearRect({x:options.x,y:options.y,w:options.w,h:options.h,r:8});
|
||||||
|
|
||||||
|
if (info.img) {
|
||||||
|
g.drawImage(info.img, options.x+1,options.y+2);
|
||||||
|
}
|
||||||
|
var text = info.text.toString().toUpperCase();
|
||||||
|
if (g.setFont("7Seg:2").stringWidth(text)+24-2>options.w) g.setFont("7Seg");
|
||||||
|
g.setFontAlign(0,-1).drawString(text, options.x+options.w/2+13, options.y+6);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "clock",
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
delete Graphics.prototype.setFont7Seg;
|
||||||
|
// remove info menu
|
||||||
|
clockInfoMenu.remove();
|
||||||
|
delete clockInfoMenu;
|
||||||
|
clockInfoMenu2.remove();
|
||||||
|
delete clockInfoMenu2;
|
||||||
|
clockInfoMenu3.remove();
|
||||||
|
delete clockInfoMenu3;
|
||||||
|
clockInfoMenu4.remove();
|
||||||
|
delete clockInfoMenu4;
|
||||||
|
// reset theme
|
||||||
|
g.setTheme(oldTheme);
|
||||||
|
}});
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
// Work out sizes
|
||||||
|
let R = Bangle.appRect;
|
||||||
|
R.x+=1;
|
||||||
|
R.y+=1;
|
||||||
|
R.x2-=1;
|
||||||
|
R.y2-=1;
|
||||||
|
R.w-=1;
|
||||||
|
R.h-=1;
|
||||||
|
let midX = R.x+R.w/2;
|
||||||
|
let bar1Y = R.y+30;
|
||||||
|
let bar2Y = R.y2-30;
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
let oldTheme = g.theme;
|
||||||
|
g.setTheme({bg:"#000",fg:"#3ff",dark:true}).clear(1);
|
||||||
|
g.fillRect({x:R.x, y:R.y, w:R.w, h:R.h, r:8})
|
||||||
|
.clearRect(R.x,bar1Y,R.w,bar1Y+1)
|
||||||
|
.clearRect(midX,R.y,midX,bar1Y)
|
||||||
|
.clearRect(R.x,bar2Y,R.w,bar2Y+1)
|
||||||
|
.clearRect(midX,bar2Y,midX,R.y2+1);
|
||||||
|
draw();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
// Allocate and draw clockinfos
|
||||||
|
let clockInfoItems = require("clock_info").load();
|
||||||
|
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x, y:R.y, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
|
||||||
|
let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+1, y:R.y, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
|
||||||
|
let clockInfoMenu3 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x, y:bar2Y+2, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
|
||||||
|
let clockInfoMenu4 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+1, y:bar2Y+2, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
|
||||||
|
}
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,16 @@
|
||||||
|
{ "id": "lcdclockplus",
|
||||||
|
"name": "LCD Clock Plus",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A Casio-style clock, with four ClockInfo areas at the top and bottom. Tap them and swipe up/down and left/right to toggle between different information.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock,clkinfo",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"dependencies" : { "clock_info":"module" },
|
||||||
|
"storage": [
|
||||||
|
{"name":"lcdclockplus.app.js","url":"app.js"},
|
||||||
|
{"name":"lcdclockplus.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -5,3 +5,5 @@
|
||||||
0.05: Prevent drawing into app area.
|
0.05: Prevent drawing into app area.
|
||||||
0.06: Fix issue where .draw was being called by reference (not allowing widgets to be hidden)
|
0.06: Fix issue where .draw was being called by reference (not allowing widgets to be hidden)
|
||||||
0.07: Handle the swipe event that is generated when draging to change light intensity, so it doesn't trigger some other swipe handler.
|
0.07: Handle the swipe event that is generated when draging to change light intensity, so it doesn't trigger some other swipe handler.
|
||||||
|
0.08: Ensure boot code doesn't allocate and leave a gloval variable named 'settings'
|
||||||
|
0.09: Handle lightswitch logic running before its widget has loaded
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
{
|
||||||
// load settings
|
// load settings
|
||||||
var settings = Object.assign({
|
let settings = Object.assign({
|
||||||
value: 1,
|
value: 1,
|
||||||
isOn: true
|
isOn: true
|
||||||
}, require("Storage").readJSON("lightswitch.json", true) || {});
|
}, require("Storage").readJSON("lightswitch.json", true) || {});
|
||||||
|
@ -12,6 +13,4 @@ Bangle.removeListener("tap", require("lightswitch.js").tapListener);
|
||||||
|
|
||||||
// add tap listener to unlock and/or flash backlight
|
// add tap listener to unlock and/or flash backlight
|
||||||
if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
|
if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
|
||||||
|
}
|
||||||
// clear variable
|
|
||||||
settings = undefined;
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ exports = {
|
||||||
// check for double tap and direction
|
// check for double tap and direction
|
||||||
if (data.double) {
|
if (data.double) {
|
||||||
// setup shortcut to this widget or load from storage
|
// setup shortcut to this widget or load from storage
|
||||||
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({
|
var w = global.WIDGETS && WIDGETS.lightswitch || Object.assign({
|
||||||
unlockSide: "",
|
unlockSide: "",
|
||||||
tapSide: "right",
|
tapSide: "right",
|
||||||
tapOn: "always",
|
tapOn: "always",
|
||||||
|
@ -31,7 +31,7 @@ exports = {
|
||||||
// function to flash backlight
|
// function to flash backlight
|
||||||
flash: function(tOut) {
|
flash: function(tOut) {
|
||||||
// setup shortcut to this widget or load from storage
|
// setup shortcut to this widget or load from storage
|
||||||
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({
|
var w = global.WIDGETS && WIDGETS.lightswitch || Object.assign({
|
||||||
tOut: 3000,
|
tOut: 3000,
|
||||||
minFlash: 0.2,
|
minFlash: 0.2,
|
||||||
value: 1,
|
value: 1,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "lightswitch",
|
"id": "lightswitch",
|
||||||
"name": "Light Switch Widget",
|
"name": "Light Switch Widget",
|
||||||
"shortName": "Light Switch",
|
"shortName": "Light Switch",
|
||||||
"version": "0.07",
|
"version": "0.09",
|
||||||
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
|
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
|
||||||
"icon": "images/app.png",
|
"icon": "images/app.png",
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<style>
|
||||||
|
table { width:100%;}
|
||||||
|
.table_t {font-weight:bold;width:40%;};
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
@ -13,6 +17,11 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input id="translations" type="checkbox" /> <label for="translations">Add common language translations like "Yes", "No", "On", "Off"<br/><i>(Not recommended. For translations use the option under <code>More...</code> in the app loader.</i></label>
|
<input id="translations" type="checkbox" /> <label for="translations">Add common language translations like "Yes", "No", "On", "Off"<br/><i>(Not recommended. For translations use the option under <code>More...</code> in the app loader.</i></label>
|
||||||
</div>
|
</div>
|
||||||
|
<p>
|
||||||
|
<table id="examples">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</table>
|
||||||
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
|
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||||
|
|
||||||
<script src="../../core/lib/customize.js"></script>
|
<script src="../../core/lib/customize.js"></script>
|
||||||
|
@ -93,21 +102,8 @@ exports = { name : "en_GB", currencySym:"£",
|
||||||
checkChars(locale,localeName);
|
checkChars(locale,localeName);
|
||||||
});
|
});
|
||||||
|
|
||||||
var languageSelector = document.getElementById("languages");
|
|
||||||
languageSelector.innerHTML = Object.keys(locales).map(l=>{
|
|
||||||
var localeParts = l.split("_"); // en_GB -> ["en","GB"]
|
|
||||||
var icon = "";
|
|
||||||
// If we have a 2 char ISO country code, use it to get the unicode flag
|
|
||||||
if (localeParts[1] && localeParts[1].length==2)
|
|
||||||
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
|
|
||||||
if (localeParts[1]=="NAV")
|
|
||||||
icon = "⛵✈️ ";
|
|
||||||
return `<option value="${l}">${icon}${l}</option>`
|
|
||||||
}).join("\n");
|
|
||||||
|
|
||||||
document.getElementById("upload").addEventListener("click", function() {
|
function createLocaleModule(lang) {
|
||||||
|
|
||||||
const lang = languageSelector.options[languageSelector.selectedIndex].value;
|
|
||||||
console.log(`Language ${lang}`);
|
console.log(`Language ${lang}`);
|
||||||
|
|
||||||
const translations = document.getElementById('translations').checked;
|
const translations = document.getElementById('translations').checked;
|
||||||
|
@ -151,7 +147,7 @@ exports = { name : "en_GB", currencySym:"£",
|
||||||
|
|
||||||
var replaceList = {
|
var replaceList = {
|
||||||
"%Y": "d.getFullYear()",
|
"%Y": "d.getFullYear()",
|
||||||
"%y": "(d.getFullYear().toString()).slice(-2)",
|
"%y": "d.getFullYear().toString().slice(-2)",
|
||||||
"%m": "('0'+(d.getMonth()+1).toString()).slice(-2)",
|
"%m": "('0'+(d.getMonth()+1).toString()).slice(-2)",
|
||||||
"%-m": "d.getMonth()+1",
|
"%-m": "d.getMonth()+1",
|
||||||
"%d": "('0'+d.getDate()).slice(-2)",
|
"%d": "('0'+d.getDate()).slice(-2)",
|
||||||
|
@ -182,16 +178,17 @@ exports = { name : "en_GB", currencySym:"£",
|
||||||
`exports.number(n) + ${js(locale.currency_symbol)}`;
|
`exports.number(n) + ${js(locale.currency_symbol)}`;
|
||||||
var temperature = locale.temperature=='°F' ? '(t*9/5)+32' : 't';
|
var temperature = locale.temperature=='°F' ? '(t*9/5)+32' : 't';
|
||||||
|
|
||||||
var localeModule = `
|
function getLocaleModule(isLocal) {
|
||||||
|
return `
|
||||||
function round(n, dp) {
|
function round(n, dp) {
|
||||||
if (dp===undefined) dp=0;
|
if (dp===undefined) dp=0;
|
||||||
var p = Math.min(dp,dp - Math.floor(Math.log(n)/Math.log(10)));
|
var p = Math.max(0,Math.min(dp,dp - Math.floor(Math.log(n)/Math.log(10))));
|
||||||
return n.toFixed(p);
|
return n.toFixed(p);
|
||||||
}
|
}
|
||||||
var is12;
|
var is12;
|
||||||
function getHours(d) {
|
function getHours(d) {
|
||||||
var h = d.getHours();
|
var h = d.getHours();
|
||||||
if (is12 === undefined) is12 = (require('Storage').readJSON('setting.json', 1) || {})["12hour"];
|
if (is12 === undefined) is12 = ${isLocal ? "false" : `(require('Storage').readJSON('setting.json', 1) || {})["12hour"]`};
|
||||||
if (!is12) return ('0' + h).slice(-2);
|
if (!is12) return ('0' + h).slice(-2);
|
||||||
return ((h % 12 == 0) ? 12 : h % 12).toString();
|
return ((h % 12 == 0) ? 12 : h % 12).toString();
|
||||||
}
|
}
|
||||||
|
@ -224,7 +221,54 @@ exports = {
|
||||||
time: (d,short) => short ? \`${timeS}\` : \`${timeN}\`,
|
time: (d,short) => short ? \`${timeS}\` : \`${timeN}\`,
|
||||||
meridian: d => d.getHours() < 12 ? ${js(locale.ampm[0])}:${js(locale.ampm[1])},
|
meridian: d => d.getHours() < 12 ? ${js(locale.ampm[0])}:${js(locale.ampm[1])},
|
||||||
};
|
};
|
||||||
`.trim();
|
`.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
var exports;
|
||||||
|
eval(getLocaleModule(true));
|
||||||
|
console.log("exports:",exports);
|
||||||
|
|
||||||
|
var date = new Date();
|
||||||
|
document.getElementById("examples").innerHTML = `
|
||||||
|
<tr><td class="table_t"></td><td style="font-weight:bold">Short</td><td style="font-weight:bold">Long</td></tr>
|
||||||
|
<tr><td class="table_t">Day</td><td>${exports.dow(date,1)}</td><td>${exports.dow(date,0)}</td></tr>
|
||||||
|
<tr><td class="table_t">Month</td><td>${exports.month(date,1)}</td><td>${exports.month(date,0)}</td></tr>
|
||||||
|
<tr><td class="table_t">Date</td><td>${exports.date(date,1)}</td><td>${exports.date(date,0)}</td></tr>
|
||||||
|
<tr><td class="table_t">Time</td><td>${exports.time(date,1)}</td><td>${exports.time(date,0)}</td></tr>
|
||||||
|
<tr><td class="table_t">Number</td><td>${exports.number(12.3456789)}</td><td>${exports.number(12.3456789,4)}</td></tr>
|
||||||
|
<tr><td class="table_t">Currency</td><td></td><td>${exports.currency(12.34)}</td></tr>
|
||||||
|
<tr><td class="table_t">Distance</td><td>${exports.distance(12.34,0)}</td><td>${exports.distance(12345.6,1)}</td></tr>
|
||||||
|
<tr><td class="table_t">Speed</td><td></td><td>${exports.speed(123)}</td></tr>
|
||||||
|
<tr><td class="table_t">Temperature</td><td></td><td>${exports.temp(12,0)}</td></tr>
|
||||||
|
`;
|
||||||
|
return getLocaleModule(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var languageSelector = document.getElementById("languages");
|
||||||
|
languageSelector.innerHTML = Object.keys(locales).map(l=>{
|
||||||
|
var locale = locales[l];
|
||||||
|
var localeParts = l.split("_"); // en_GB -> ["en","GB"]
|
||||||
|
var icon = "";
|
||||||
|
// If we have a 2 char ISO country code, use it to get the unicode flag
|
||||||
|
if (locale.icon)
|
||||||
|
icon = locale.icon+" ";
|
||||||
|
else if (localeParts[1] && localeParts[1].length==2)
|
||||||
|
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
|
||||||
|
return `<option value="${l}">${icon}${l}${locale.notes?" - "+locale.notes:""}</option>`
|
||||||
|
}).join("\n");
|
||||||
|
languageSelector.addEventListener('change', function() {
|
||||||
|
const lang = languageSelector.options[languageSelector.selectedIndex].value;
|
||||||
|
createLocaleModule(lang);
|
||||||
|
});
|
||||||
|
// initial value
|
||||||
|
createLocaleModule(languageSelector.options[languageSelector.selectedIndex].value);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("upload").addEventListener("click", function() {
|
||||||
|
|
||||||
|
const lang = languageSelector.options[languageSelector.selectedIndex].value;
|
||||||
|
var localeModule = createLocaleModule(lang);
|
||||||
|
|
||||||
console.log("Locale Module is:",localeModule);
|
console.log("Locale Module is:",localeModule);
|
||||||
sendCustomizedApp({
|
sendCustomizedApp({
|
||||||
|
|
|
@ -79,6 +79,44 @@ var locales = {
|
||||||
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||||
// No translation for english...
|
// No translation for english...
|
||||||
},
|
},
|
||||||
|
"en_US": {
|
||||||
|
lang: "en_US",
|
||||||
|
notes: "USA with MM/DD/YY date",
|
||||||
|
decimal_point: ".",
|
||||||
|
thousands_sep: ",",
|
||||||
|
currency_symbol: "$", currency_first: true,
|
||||||
|
int_curr_symbol: "USD",
|
||||||
|
speed: "mph",
|
||||||
|
distance: { 0: "ft", 1: "mi" },
|
||||||
|
temperature: "°F",
|
||||||
|
ampm: { 0: "am", 1: "pm" },
|
||||||
|
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
||||||
|
datePattern: { 0: "%b %d, %Y", 1: "%m/%d/%y" },
|
||||||
|
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||||
|
month: "January,February,March,April,May,June,July,August,September,October,November,December",
|
||||||
|
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
|
||||||
|
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||||
|
// No translation for english...
|
||||||
|
},
|
||||||
|
"en_US 2": {
|
||||||
|
lang: "en_US 2", icon:"🇺🇸",
|
||||||
|
notes: "USA with YYYY-MM-DD date",
|
||||||
|
decimal_point: ".",
|
||||||
|
thousands_sep: ",",
|
||||||
|
currency_symbol: "$", currency_first: true,
|
||||||
|
int_curr_symbol: "USD",
|
||||||
|
speed: "mph",
|
||||||
|
distance: { 0: "ft", 1: "mi" },
|
||||||
|
temperature: "°F",
|
||||||
|
ampm: { 0: "am", 1: "pm" },
|
||||||
|
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
||||||
|
datePattern: { 0: "%b %d, %Y", 1: "%Y-%m-%d" },
|
||||||
|
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||||
|
month: "January,February,March,April,May,June,July,August,September,October,November,December",
|
||||||
|
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
|
||||||
|
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||||
|
// No translation for english...
|
||||||
|
},
|
||||||
"en_IN": {
|
"en_IN": {
|
||||||
lang: "en_IN",
|
lang: "en_IN",
|
||||||
decimal_point: ".",
|
decimal_point: ".",
|
||||||
|
@ -118,7 +156,7 @@ var locales = {
|
||||||
// No translation for english...
|
// No translation for english...
|
||||||
},
|
},
|
||||||
"en_NAV": { // navigation units nautical miles and knots
|
"en_NAV": { // navigation units nautical miles and knots
|
||||||
lang: "en_NAV",
|
lang: "en_NAV", icon: "⛵✈️",
|
||||||
decimal_point: ".",
|
decimal_point: ".",
|
||||||
thousands_sep: ",",
|
thousands_sep: ",",
|
||||||
currency_symbol: "£", currency_first: true,
|
currency_symbol: "£", currency_first: true,
|
||||||
|
@ -154,24 +192,6 @@ var locales = {
|
||||||
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
|
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
|
||||||
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
|
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
|
||||||
},
|
},
|
||||||
"en_US": {
|
|
||||||
lang: "en_US",
|
|
||||||
decimal_point: ".",
|
|
||||||
thousands_sep: ",",
|
|
||||||
currency_symbol: "$", currency_first: true,
|
|
||||||
int_curr_symbol: "USD",
|
|
||||||
speed: "mph",
|
|
||||||
distance: { 0: "ft", 1: "mi" },
|
|
||||||
temperature: "°F",
|
|
||||||
ampm: { 0: "am", 1: "pm" },
|
|
||||||
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
|
||||||
datePattern: { 0: "%b %d, %Y", 1: "%m/%d/%y" },
|
|
||||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
|
||||||
month: "January,February,March,April,May,June,July,August,September,October,November,December",
|
|
||||||
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
|
|
||||||
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
|
||||||
// No translation for english...
|
|
||||||
},
|
|
||||||
"en_JP": { // we do not have the font, so it is not ja_JP
|
"en_JP": { // we do not have the font, so it is not ja_JP
|
||||||
lang: "en_JP",
|
lang: "en_JP",
|
||||||
decimal_point: ".",
|
decimal_point: ".",
|
||||||
|
|
|
@ -95,17 +95,7 @@
|
||||||
Util.showModal("Downloading...");
|
Util.showModal("Downloading...");
|
||||||
let medicalInfoJson = getEditableContent();
|
let medicalInfoJson = getEditableContent();
|
||||||
if (isJsonString(medicalInfoJson)) {
|
if (isJsonString(medicalInfoJson)) {
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(medicalInfoFile, "application/json", medicalInfoJson);
|
||||||
file = new Blob([medicalInfoJson], { type: "application/json" });
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = medicalInfoFile;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function () {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("info").innerHTML = errorFormat();
|
document.getElementById("info").innerHTML = errorFormat();
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,4 +99,5 @@
|
||||||
0.71: Cancel buzzing when watch unlocked, or when different messages viewed
|
0.71: Cancel buzzing when watch unlocked, or when different messages viewed
|
||||||
On 2v18.64+ firmware, 'No Messages' now has a 'back' button
|
On 2v18.64+ firmware, 'No Messages' now has a 'back' button
|
||||||
0.72: Nav message updastes don't automatically launch navigation menu unless they're new
|
0.72: Nav message updastes don't automatically launch navigation menu unless they're new
|
||||||
0.73: Add sharp left+right nav icons
|
0.73: Add sharp left+right nav icons
|
||||||
|
0.74: Add option for driving on left (affects roundabout icons in navigation)
|
|
@ -98,6 +98,7 @@ function showMapMessage(msg) {
|
||||||
}else
|
}else
|
||||||
target = instr;
|
target = instr;
|
||||||
}
|
}
|
||||||
|
var carIsRHD = !!settings.carIsRHD;
|
||||||
switch (msg.action) {
|
switch (msg.action) {
|
||||||
case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break;
|
case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break;
|
||||||
case "left": img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";break;
|
case "left": img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";break;
|
||||||
|
@ -111,12 +112,11 @@ function showMapMessage(msg) {
|
||||||
case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break;
|
case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break;
|
||||||
case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break;
|
case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break;
|
||||||
case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break;
|
case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break;
|
||||||
case "roundabout_left": img = "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=";break;
|
case "roundabout_left": img = carIsRHD ? "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=" : "HRYCAAPAAAAAAAAD/AAD//AAA/8AD///AAP/AA////AD/wAD/wP/A/8AA/wAP8P/////AAP//////8AA///////AAD/P////8AAP8P/AABUAD/AP/AAFUA/8AP/AAFX//AAP/AAFf/wAAP8AAB/8AAAPAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAA==";break;
|
||||||
case "roundabout_right": img = "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=";break;
|
case "roundabout_right": img = carIsRHD ? "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=" : "HBYCAAAAAAPAAABVQAAP8AAFVVQAD/wAFVVVAAP/ABUAFQAA/8BUAAVAAD/wVAAP/////FAAD/////9QAA//////VAAP/////FQAP8AAP/AVAP/AAP/AFX//AAP/AAV//AAP/AAAf/AAD/AAAD/AAAPAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAA==";break;
|
||||||
case "roundabout_straight": img = "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==";break;
|
case "roundabout_straight": img = carIsRHD ? "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==" : "EBsCAAPAAAAP8AAAP/wAAP//AAP//8AP///wP8/z/P8P8P/8D/A/MA/wDABf/wABX//ABV//8BVAD/wVAAP8FQAA/BUAAPwVAAD8FQAA/BUAA/wVQA/8BV//8AFf/8AAX/8AAA/wAAAP8AAAD/AA";break;
|
||||||
case "roundabout_uturn": img = "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA";break;
|
case "roundabout_uturn": img = carIsRHD ? "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA" : "ICABAAAAAAAAAAAAAAAAAAfwAAAP+AAAH/wAAD4eAAA8DwAAOA8AAHgHAAB4BwAAOA8AADwPAAA+HgAAHzwAAA84AAAPIAAADwAAAY8YAAPPPAAB73wAAf/4AAD/8AAAP+gAAB/YAAAPuAAADzgAAAY4AAAAAAAAAAAAAAAAAAAAAAA=";break;
|
||||||
}
|
}
|
||||||
//FIXME: what about countries where we drive on the right? How will we know to flip the icons?
|
|
||||||
|
|
||||||
layout = new Layout({ type:"v", c: [
|
layout = new Layout({ type:"v", c: [
|
||||||
{type:"txt", font:street?fontMedium:fontLarge, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:3 },
|
{type:"txt", font:street?fontMedium:fontLarge, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:3 },
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "messagegui",
|
"id": "messagegui",
|
||||||
"name": "Message UI",
|
"name": "Message UI",
|
||||||
"shortName": "Messages",
|
"shortName": "Messages",
|
||||||
"version": "0.73",
|
"version": "0.74",
|
||||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
0.59: fixes message timeout by using setinterval, as it was intended. So the buzz is triggered every x seconds until the timeout occours.
|
0.59: fixes message timeout by using setinterval, as it was intended. So the buzz is triggered every x seconds until the timeout occours.
|
||||||
0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called
|
0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called
|
||||||
0.61: Add repeatCalls option to allow different repeat settings for messages vs calls
|
0.61: Add repeatCalls option to allow different repeat settings for messages vs calls
|
||||||
|
0.62: Add option for driving on left (affects roundabout icons in navigation)
|
|
@ -43,6 +43,28 @@ with a method called `open` that will cause it to be opened, with the
|
||||||
optionally supplied message. See `apps/messagegui/lib.js` for an example.
|
optionally supplied message. See `apps/messagegui/lib.js` for an example.
|
||||||
|
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
You can configure settings by going to `Settings -> Apps -> Messages`
|
||||||
|
|
||||||
|
There are several options to choose from:
|
||||||
|
|
||||||
|
* **Vibrate** : Vibration pattern to use for messages
|
||||||
|
* **Vibrate for calls** : Vibration pattern to use for calls
|
||||||
|
* **Repeat** : How many times to vibrate for messages
|
||||||
|
* **Repeat for calls** : How many times to vibrate for calls
|
||||||
|
* **Vibrate timer** : How many seconds should we vibrate for?
|
||||||
|
* **Unread timer** : How long should the Messages app show an unread message for before going back to the clock?
|
||||||
|
* **Min Font** : Minimum font size for messages
|
||||||
|
* **Auto-Open Music** : Should the messages music screen auto open when music is played?
|
||||||
|
* **Unlock Watch** : When a message arrives should the watch be unlocked?
|
||||||
|
* **Flash Icon** : Should the messages icon in the widget flash when a message arrives?
|
||||||
|
* **Quiet mode disables auto-open** : When in quiet mode, should we not open the messages app for new messages?
|
||||||
|
* **Disable auto-open** : Should we not open the messages app for new messages?
|
||||||
|
* **Widget messages** : How many message icons should the widget show?
|
||||||
|
* **Icon color mode** : Should icons in widgets be coloured?
|
||||||
|
* **Car driver pos** : What side of the car is the driver on? This affects navigation icons for roundabouts
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=[messages]%20library
|
Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=[messages]%20library
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "messages",
|
"id": "messages",
|
||||||
"name": "Messages",
|
"name": "Messages",
|
||||||
"version": "0.61",
|
"version": "0.62",
|
||||||
"description": "Library to handle, load and store message events received from Android/iOS",
|
"description": "Library to handle, load and store message events received from Android/iOS",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
@ -89,7 +89,12 @@
|
||||||
min: 0, max: iconColorModes.length - 1,
|
min: 0, max: iconColorModes.length - 1,
|
||||||
format: v => iconColorModes[v],
|
format: v => iconColorModes[v],
|
||||||
onchange: v => updateSetting("iconColorMode", iconColorModes[v])
|
onchange: v => updateSetting("iconColorMode", iconColorModes[v])
|
||||||
}
|
},
|
||||||
|
/*LANG*/'Car driver pos': { // used by messagegui
|
||||||
|
value:!!settings().carIsRHD,
|
||||||
|
format: v => v ? /*LANG*/"Right" :/*LANG*/"Left",
|
||||||
|
onchange: v => updateSetting("carIsRHD", v)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,17 +96,7 @@
|
||||||
Util.showModal("Downloading...");
|
Util.showModal("Downloading...");
|
||||||
let csvTimes = getEditableContent();
|
let csvTimes = getEditableContent();
|
||||||
if (isCorrectCsvString(csvTimes)) {
|
if (isCorrectCsvString(csvTimes)) {
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(filePresentationTimer, "text/csv", csvTimes);
|
||||||
file = new Blob([csvTimes], { type: "text/csv" });
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = filePresentationTimer;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function () {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("info").innerHTML = errorFormat();
|
document.getElementById("info").innerHTML = errorFormat();
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,17 +75,7 @@ ${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
|
||||||
</Folder>
|
</Folder>
|
||||||
</Document>
|
</Document>
|
||||||
</kml>`;
|
</kml>`;
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(title+".kml", "application/vnd.google-earth.kml+xml", kml);
|
||||||
file = new Blob([kml], {type: "application/vnd.google-earth.kml+xml"});
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = title+".kml";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function() {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
showToast("Download finished.", "success");
|
showToast("Download finished.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,17 +112,7 @@ function saveGPX(track, title) {
|
||||||
</trkseg>
|
</trkseg>
|
||||||
</trk>
|
</trk>
|
||||||
</gpx>`;
|
</gpx>`;
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(title+".gpx", "application/gpx+xml", gpx);
|
||||||
file = new Blob([gpx], {type: "application/gpx+xml"});
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = title+".gpx";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function() {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
showToast("Download finished.", "success");
|
showToast("Download finished.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Update help screen with more details.
|
0.03: Update help screen with more details.
|
||||||
0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game.
|
0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game.
|
||||||
0.05: add confirmation prompt to new game to prevent fat fingering new game during existing one.
|
0.05: add confirmation prompt to new game to prevent fat fingering new game during existing one.
|
||||||
|
0.06: fix AI logic typo and add prompt to show what AI played each turn.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Red 7 Card Game",
|
"name": "Red 7 Card Game",
|
||||||
"shortName" : "Red 7",
|
"shortName" : "Red 7",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
|
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
|
||||||
"tags": "game",
|
"tags": "game",
|
||||||
"supports":["BANGLEJS2"],
|
"supports":["BANGLEJS2"],
|
||||||
|
|
|
@ -17,6 +17,9 @@ class Card {
|
||||||
//this.rect = {};
|
//this.rect = {};
|
||||||
this.clippedRect = {};
|
this.clippedRect = {};
|
||||||
}
|
}
|
||||||
|
get description() {
|
||||||
|
return this.cardColor+" "+this.cardNum;
|
||||||
|
}
|
||||||
get number() {
|
get number() {
|
||||||
return this.cardNum;
|
return this.cardNum;
|
||||||
}
|
}
|
||||||
|
@ -514,7 +517,7 @@ class AI {
|
||||||
//Play card that wins
|
//Play card that wins
|
||||||
this.palette.addCard(c);
|
this.palette.addCard(c);
|
||||||
this.hand.removeCard(c);
|
this.hand.removeCard(c);
|
||||||
return true;
|
return { winning: true, paletteAdded: c };
|
||||||
}
|
}
|
||||||
clonePalette.removeCard(c);
|
clonePalette.removeCard(c);
|
||||||
}
|
}
|
||||||
|
@ -524,26 +527,26 @@ class AI {
|
||||||
//Play rule card that wins
|
//Play rule card that wins
|
||||||
ruleStack.addCard(c);
|
ruleStack.addCard(c);
|
||||||
this.hand.removeCard(c);
|
this.hand.removeCard(c);
|
||||||
return true;
|
return { winning: true, ruleAdded: c };
|
||||||
} else {
|
} else {
|
||||||
//Check if any palette play can win with rule.
|
//Check if any palette play can win with rule.
|
||||||
for(let h of this.hand.handCards) {
|
for(let h of this.hand.handCards) {
|
||||||
if(h === c) {}
|
if(h === c) {}
|
||||||
else {
|
else {
|
||||||
clonePalette.addCard(c);
|
clonePalette.addCard(h);
|
||||||
if(isWinningCombo(c, clonePalette, otherPalette)) {
|
if(isWinningCombo(c, clonePalette, otherPalette)) {
|
||||||
ruleStack.addCard(c);
|
ruleStack.addCard(c);
|
||||||
this.hand.removeCard(c);
|
this.hand.removeCard(c);
|
||||||
this.palette.addCard(h);
|
this.palette.addCard(h);
|
||||||
this.hand.removeCard(h);
|
this.hand.removeCard(h);
|
||||||
return true;
|
return { winning: true, ruleAdded: c, paletteAdded: h };
|
||||||
}
|
}
|
||||||
clonePalette.removeCard(c);
|
clonePalette.removeCard(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return { winning: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -726,18 +729,20 @@ function finishTurn() {
|
||||||
if(AIhand.handCards.length === 0) {
|
if(AIhand.handCards.length === 0) {
|
||||||
drawGameOver(true);
|
drawGameOver(true);
|
||||||
} else {
|
} else {
|
||||||
var takenTurn = aiPlayer.takeTurn(ruleCards, playerPalette);
|
var aiResult = aiPlayer.takeTurn(ruleCards, playerPalette);
|
||||||
//Check if game over conditions met.
|
E.showPrompt("AI played: " + ("paletteAdded" in aiResult ? aiResult["paletteAdded"].description+" to pallete. ":"") + ("ruleAdded" in aiResult ? aiResult["ruleAdded"].description+" to rules.":""),{buttons: {"Ok":0}}).then(function(){
|
||||||
if(!takenTurn) {
|
//Check if game over conditions met.
|
||||||
drawGameOver(true);
|
if(!aiResult["winning"]) {
|
||||||
} else if(playerHand.handCards.length === 0) {
|
drawGameOver(true);
|
||||||
drawGameOver(false);
|
} else if(playerHand.handCards.length === 0) {
|
||||||
} else if(!canPlay(playerHand, playerPalette, AIPalette)) {
|
drawGameOver(false);
|
||||||
drawGameOver(false);
|
} else if(!canPlay(playerHand, playerPalette, AIPalette)) {
|
||||||
} else {
|
drawGameOver(false);
|
||||||
E.showMenu();
|
} else {
|
||||||
drawScreen1();
|
E.showMenu();
|
||||||
}
|
drawScreen1();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -843,4 +848,3 @@ drawMainMenu();
|
||||||
setWatch(function(){
|
setWatch(function(){
|
||||||
drawMainMenu();
|
drawMainMenu();
|
||||||
},BTN, {edge: "rising", debounce: 50, repeat: true});
|
},BTN, {edge: "rising", debounce: 50, repeat: true});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: use binary format, include world map
|
|
@ -38,6 +38,8 @@ Directories at the end of .mtar should be hashed, not linear searched.
|
||||||
|
|
||||||
Geojson is not really suitable as it takes a lot of storage.
|
Geojson is not really suitable as it takes a lot of storage.
|
||||||
|
|
||||||
It would be nice to support polygons.
|
Web-based tool for preparing maps would be nice.
|
||||||
|
|
||||||
Web-based tool for preparing maps would be nice.
|
Storing 12bit coordinates, but only using 8bits.
|
||||||
|
|
||||||
|
Polygons should go first to get proper z-order.
|
|
@ -386,7 +386,7 @@ function emptyMap() {
|
||||||
m.scale = 2;
|
m.scale = 2;
|
||||||
g.reset().clearRect(R);
|
g.reset().clearRect(R);
|
||||||
redraw(18);
|
redraw(18);
|
||||||
print("Benchmark done (31 sec)");
|
print("Benchmark done");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{
|
if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{
|
||||||
|
@ -400,36 +400,52 @@ function emptyMap() {
|
||||||
|
|
||||||
var gjson = null;
|
var gjson = null;
|
||||||
|
|
||||||
|
function stringFromArray(data) {
|
||||||
|
var count = data.length;
|
||||||
|
var str = "";
|
||||||
|
|
||||||
|
for(var index = 0; index < count; index += 1)
|
||||||
|
str += String.fromCharCode(data[index]);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
const st = require('Storage');
|
||||||
|
const hs = require('heatshrink');
|
||||||
|
|
||||||
function readTarFile(tar, f) {
|
function readTarFile(tar, f) {
|
||||||
const st = require('Storage');
|
let json_off = st.read(tar, 0, 16) * 1;
|
||||||
json_off = st.read(tar, 0, 16) * 1;
|
|
||||||
if (isNaN(json_off)) {
|
if (isNaN(json_off)) {
|
||||||
print("Don't have archive", tar);
|
print("Don't have archive", tar);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
while (1) {
|
while (1) {
|
||||||
json_len = st.read(tar, json_off, 6) * 1;
|
let json_len = st.read(tar, json_off, 6) * 1;
|
||||||
if (json_len == -1)
|
if (json_len == -1)
|
||||||
break;
|
break;
|
||||||
json_off += 6;
|
json_off += 6;
|
||||||
json = st.read(tar, json_off, json_len);
|
let json = st.read(tar, json_off, json_len);
|
||||||
//print("Have directory, ", json.length, "bytes");
|
//print("Have directory, ", json.length, "bytes");
|
||||||
//print(json);
|
let files = JSON.parse(json);
|
||||||
files = JSON.parse(json);
|
let rec = files[f];
|
||||||
//print(files);
|
if (rec) {
|
||||||
rec = files[f];
|
let cs = st.read(tar, rec.st, rec.si);
|
||||||
if (rec)
|
if (rec.comp == "hs") {
|
||||||
return st.read(tar, rec.st, rec.si);
|
let d = stringFromArray(hs.decompress(cs));
|
||||||
|
//print("Decompressed", d);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
json_off += json_len;
|
json_off += json_len;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadVector(name) {
|
function loadVector(name) {
|
||||||
var t1 = getTime();
|
var t1 = getTime();
|
||||||
print(".. Read", name);
|
print(".. Read", name);
|
||||||
//s = require("Storage").read(name);
|
//s = require("Storage").read(name);
|
||||||
var s = readTarFile("delme.mtar", name);
|
var s = readTarFile("world.mtar", name);
|
||||||
if (s == undefined) {
|
if (s == undefined) {
|
||||||
print("Don't have file", name);
|
print("Don't have file", name);
|
||||||
return null;
|
return null;
|
||||||
|
@ -438,8 +454,7 @@ function loadVector(name) {
|
||||||
print(".... Read and parse took ", getTime()-t1);
|
print(".... Read and parse took ", getTime()-t1);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
function drawPoint(a) { /* FIXME: let... */
|
||||||
function drawPoint(a) {
|
|
||||||
lon = a.geometry.coordinates[0];
|
lon = a.geometry.coordinates[0];
|
||||||
lat = a.geometry.coordinates[1];
|
lat = a.geometry.coordinates[1];
|
||||||
|
|
||||||
|
@ -459,7 +474,6 @@ function drawPoint(a) {
|
||||||
g.drawString(a.properties.name, p.x, p.y);
|
g.drawString(a.properties.name, p.x, p.y);
|
||||||
points ++;
|
points ++;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawLine(a, qual) {
|
function drawLine(a, qual) {
|
||||||
lon = a.geometry.coordinates[0][0];
|
lon = a.geometry.coordinates[0][0];
|
||||||
lat = a.geometry.coordinates[0][1];
|
lat = a.geometry.coordinates[0][1];
|
||||||
|
@ -485,38 +499,196 @@ function drawLine(a, qual) {
|
||||||
i = len-1;
|
i = len-1;
|
||||||
points ++;
|
points ++;
|
||||||
p1 = p2;
|
p1 = p2;
|
||||||
g.flip();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function drawPolygon(a, qual) {
|
||||||
|
lon = a.geometry.coordinates[0][0];
|
||||||
|
lat = a.geometry.coordinates[0][1];
|
||||||
|
i = 1;
|
||||||
|
step = 1;
|
||||||
|
len = a.geometry.coordinates.length;
|
||||||
|
if (len > 62) {
|
||||||
|
step = log2(len) - 5;
|
||||||
|
step = 1<<step;
|
||||||
|
}
|
||||||
|
step = step * qual;
|
||||||
|
var p1 = m.latLonToXY(lat, lon);
|
||||||
|
let pol = [p1.x, p1.y];
|
||||||
|
while (i < len) {
|
||||||
|
lon = a.geometry.coordinates[i][0];
|
||||||
|
lat = a.geometry.coordinates[i][1];
|
||||||
|
var p2 = m.latLonToXY(lat, lon);
|
||||||
|
|
||||||
function drawVector(gjson, qual) {
|
pol.push(p2.x, p2.y);
|
||||||
|
if (i == len-1)
|
||||||
|
break;
|
||||||
|
i = i + step;
|
||||||
|
if (i>len)
|
||||||
|
i = len-1;
|
||||||
|
points ++;
|
||||||
|
}
|
||||||
|
if (a.properties.fill) {
|
||||||
|
g.setColor(a.properties.fill);
|
||||||
|
} else {
|
||||||
|
g.setColor(.75, .75, 1);
|
||||||
|
}
|
||||||
|
g.fillPoly(pol, true);
|
||||||
|
if (a.properties.stroke) {
|
||||||
|
g.setColor(a.properties.stroke);
|
||||||
|
} else {
|
||||||
|
g.setColor(0,0,0)
|
||||||
|
}
|
||||||
|
g.drawPoly(pol, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toScreen(tile, xy) {
|
||||||
|
// w, s, e, n, (x,y in 0..4096 range)
|
||||||
|
let x = xy[0];
|
||||||
|
let y = xy[1];
|
||||||
|
let r = {};
|
||||||
|
r.x = ((x/4096) * (tile[2]-tile[0])) + tile[0];
|
||||||
|
r.y = ((1-(y/4096)) * (tile[3]-tile[1])) + tile[1];
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
var d_off = 1;
|
||||||
|
function getBin(bin, i, prev) {
|
||||||
|
let x = bin[i*3 + d_off ]<<4;
|
||||||
|
let y = bin[i*3 + d_off+1]<<4;
|
||||||
|
//print("Point", x, y, bin);
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
function getBinLength(bin) {
|
||||||
|
return (bin.length-d_off) / 3;
|
||||||
|
}
|
||||||
|
function newPoint(tile, a, rec, bin) {
|
||||||
|
var p = toScreen(tile, getBin(bin, 0, null));
|
||||||
|
var sz = 2;
|
||||||
|
if (a.properties) {
|
||||||
|
if (a.properties["marker-color"]) {
|
||||||
|
g.setColor(a.properties["marker-color"]);
|
||||||
|
}
|
||||||
|
if (a.properties.marker_size == "small")
|
||||||
|
sz = 1;
|
||||||
|
if (a.properties.marker_size == "large")
|
||||||
|
sz = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz);
|
||||||
|
if (rec.tags) {
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.setFont("Vector", 18).setFontAlign(-1,-1);
|
||||||
|
g.drawString(rec.tags.name, p.x, p.y);
|
||||||
|
}
|
||||||
|
points ++;
|
||||||
|
}
|
||||||
|
function newLine(tile, a, bin) {
|
||||||
|
let xy = getBin(bin, 0, null);
|
||||||
|
let i = 1;
|
||||||
|
let step = 1;
|
||||||
|
let len = getBinLength(bin);
|
||||||
|
let p1 = toScreen(tile, xy);
|
||||||
|
if (a.properties && a.properties.stroke) {
|
||||||
|
g.setColor(a.properties.stroke);
|
||||||
|
}
|
||||||
|
while (i < len) {
|
||||||
|
xy = getBin(bin, i, xy);
|
||||||
|
var p2 = toScreen(tile, xy);
|
||||||
|
|
||||||
|
//print(p1.x, p1.y, p2.x, p2.y);
|
||||||
|
g.drawLine(p1.x, p1.y, p2.x, p2.y);
|
||||||
|
if (i == len-1)
|
||||||
|
break;
|
||||||
|
i = i + step;
|
||||||
|
if (i>len)
|
||||||
|
i = len-1;
|
||||||
|
points ++;
|
||||||
|
p1 = p2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function newPolygon(tile, a, bin) {
|
||||||
|
let xy = getBin(bin, 0, null);
|
||||||
|
i = 1;
|
||||||
|
step = 1;
|
||||||
|
len = getBinLength(bin);
|
||||||
|
if (len > 62) {
|
||||||
|
step = log2(len) - 5;
|
||||||
|
step = 1<<step;
|
||||||
|
}
|
||||||
|
var p1 = toScreen(tile, xy);
|
||||||
|
let pol = [p1.x, p1.y];
|
||||||
|
while (i < len) {
|
||||||
|
xy = getBin(bin, i, xy); // FIXME... when skipping
|
||||||
|
var p2 = toScreen(tile, xy);
|
||||||
|
|
||||||
|
pol.push(p2.x, p2.y);
|
||||||
|
if (i == len-1)
|
||||||
|
break;
|
||||||
|
i = i + step;
|
||||||
|
if (i>len)
|
||||||
|
i = len-1;
|
||||||
|
points ++;
|
||||||
|
}
|
||||||
|
if (a.properties && a.properties.fill) {
|
||||||
|
g.setColor(a.properties.fill);
|
||||||
|
} else {
|
||||||
|
g.setColor(.75, .75, 1);
|
||||||
|
}
|
||||||
|
g.fillPoly(pol, true);
|
||||||
|
if (a.properties && a.properties.stroke) {
|
||||||
|
g.setColor(a.properties.stroke);
|
||||||
|
} else {
|
||||||
|
g.setColor(0,0,0)
|
||||||
|
}
|
||||||
|
g.drawPoly(pol, true);
|
||||||
|
}
|
||||||
|
function newVector(tile, rec) {
|
||||||
|
let bin = E.toUint8Array(atob(rec.b));
|
||||||
|
a = meta.attrs[bin[0]];
|
||||||
|
if (a.type == 1) {
|
||||||
|
newPoint(tile, a, rec, bin);
|
||||||
|
} else if (a.type == 2) {
|
||||||
|
newLine(tile, a, bin);
|
||||||
|
} else if (a.type == 3) {
|
||||||
|
newPolygon(tile, a, bin);
|
||||||
|
} else print("Unknown record", a);
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
function drawVector(gjson, tile, qual) {
|
||||||
var d = gjson;
|
var d = gjson;
|
||||||
points = 0;
|
points = 0;
|
||||||
var t1 = getTime();
|
var t1 = getTime();
|
||||||
|
|
||||||
for (var a of d.features) {
|
let xy1 = m.latLonToXY(tile[1], tile[0]);
|
||||||
if (a.type != "Feature")
|
let xy2 = m.latLonToXY(tile[3], tile[2]);
|
||||||
print("Expecting feature");
|
let t2 = [ xy1.x, xy1.y, xy2.x, xy2.y ];
|
||||||
|
print(t2);
|
||||||
|
|
||||||
|
for (var a of d) { // d.features for geojson
|
||||||
g.setColor(0,0,0);
|
g.setColor(0,0,0);
|
||||||
|
if (a.type != "Feature") {
|
||||||
|
newVector(t2, a);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// marker-size, marker-color, stroke
|
// marker-size, marker-color, stroke
|
||||||
if (qual < 32 && a.geometry.type == "Point")
|
if (qual < 32 && a.geometry.type == "Point")
|
||||||
drawPoint(a);
|
drawPoint(a);
|
||||||
if (qual < 8 && a.geometry.type == "LineString")
|
if (qual < 8 && a.geometry.type == "LineString")
|
||||||
drawLine(a, qual);
|
drawLine(a, qual);
|
||||||
|
if (qual < 8 && a.geometry.type == "Polygon")
|
||||||
|
drawPolygon(a, qual);
|
||||||
}
|
}
|
||||||
print("....", points, "painted in", getTime()-t1, "sec");
|
print("....", points, "painted in", getTime()-t1, "sec");
|
||||||
}
|
}
|
||||||
|
|
||||||
function fname(lon, lat, zoom) {
|
function fname(lon, lat, zoom) {
|
||||||
var bbox = [lon, lat, lon, lat];
|
var bbox = [lon, lat, lon, lat];
|
||||||
var r = xyz(bbox, 13, false, "WGS84");
|
var r = xyz(bbox, 13, false, "WGS84");
|
||||||
//console.log('fname', r);
|
//console.log('fname', r);
|
||||||
return 'z'+zoom+'-'+r.minX+'-'+r.minY+'.json';
|
return 'z'+zoom+'-'+r.minX+'-'+r.minY+'.json';
|
||||||
}
|
}
|
||||||
|
|
||||||
function fnames(zoom) {
|
function fnames(zoom) {
|
||||||
var bb = [m.lon, m.lat, m.lon, m.lat];
|
var bb = [m.lon, m.lat, m.lon, m.lat];
|
||||||
var r = xyz(bb, zoom, false, "WGS84");
|
var r = xyz(bb, zoom, false, "WGS84");
|
||||||
|
let maxt = 16;
|
||||||
while (1) {
|
while (1) {
|
||||||
var bb2 = bbox(r.minX, r.minY, zoom, false, "WGS84");
|
var bb2 = bbox(r.minX, r.minY, zoom, false, "WGS84");
|
||||||
var os = m.latLonToXY(bb2[3], bb2[0]);
|
var os = m.latLonToXY(bb2[3], bb2[0]);
|
||||||
|
@ -525,6 +697,9 @@ function fnames(zoom) {
|
||||||
else if (os.y >= 0)
|
else if (os.y >= 0)
|
||||||
r.minY -= 1;
|
r.minY -= 1;
|
||||||
else break;
|
else break;
|
||||||
|
if (!maxt)
|
||||||
|
break;
|
||||||
|
maxt--;
|
||||||
}
|
}
|
||||||
while (1) {
|
while (1) {
|
||||||
var bb2 = bbox(r.maxX, r.maxY, zoom, false, "WGS84");
|
var bb2 = bbox(r.maxX, r.maxY, zoom, false, "WGS84");
|
||||||
|
@ -534,13 +709,16 @@ function fnames(zoom) {
|
||||||
else if (os.y <= g.getHeight())
|
else if (os.y <= g.getHeight())
|
||||||
r.maxY += 1;
|
r.maxY += 1;
|
||||||
else break;
|
else break;
|
||||||
|
if (!maxt)
|
||||||
|
break;
|
||||||
|
maxt--;
|
||||||
}
|
}
|
||||||
|
if (!maxt)
|
||||||
|
print("!!! Too many tiles, not painting some");
|
||||||
print(".. paint range", r);
|
print(".. paint range", r);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
function log2(x) { return Math.log(x) / Math.log(2); }
|
function log2(x) { return Math.log(x) / Math.log(2); }
|
||||||
|
|
||||||
function getZoom(qual) {
|
function getZoom(qual) {
|
||||||
var z = 16-Math.round(log2(m.scale));
|
var z = 16-Math.round(log2(m.scale));
|
||||||
z += qual;
|
z += qual;
|
||||||
|
@ -551,7 +729,6 @@ function getZoom(qual) {
|
||||||
return meta.max_zoom;
|
return meta.max_zoom;
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawDebug(text, perc) {
|
function drawDebug(text, perc) {
|
||||||
g.setClipRect(0,0,R.x2,R.y);
|
g.setClipRect(0,0,R.x2,R.y);
|
||||||
g.reset();
|
g.reset();
|
||||||
|
@ -564,7 +741,6 @@ function drawDebug(text, perc) {
|
||||||
g.setClipRect(R.x,R.y,R.x2,R.y2);
|
g.setClipRect(R.x,R.y,R.x2,R.y2);
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawAll(qual) {
|
function drawAll(qual) {
|
||||||
var zoom = getZoom(qual);
|
var zoom = getZoom(qual);
|
||||||
var t1 = getTime();
|
var t1 = getTime();
|
||||||
|
@ -583,7 +759,7 @@ function drawAll(qual) {
|
||||||
var n ='z'+zoom+'-'+x+'-'+y+'-'+cnt+'.json';
|
var n ='z'+zoom+'-'+x+'-'+y+'-'+cnt+'.json';
|
||||||
var gjson = loadVector(n);
|
var gjson = loadVector(n);
|
||||||
if (!gjson) break;
|
if (!gjson) break;
|
||||||
drawVector(gjson, 1);
|
drawVector(gjson, bbox(x, y, zoom, false, "WGS84"), 1);
|
||||||
}
|
}
|
||||||
num++;
|
num++;
|
||||||
drawDebug("Zoom "+zoom+" tiles "+num+"/"+tiles, num/tiles);
|
drawDebug("Zoom "+zoom+" tiles "+num+"/"+tiles, num/tiles);
|
||||||
|
@ -611,7 +787,7 @@ function introScreen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
m.scale = 76;
|
m.scale = 76000;
|
||||||
m.lat = 50.001;
|
m.lat = 50.001;
|
||||||
m.lon = 14.759;
|
m.lon = 14.759;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
R = Bangle.appRect;
|
||||||
|
|
||||||
|
function introScreen() {
|
||||||
|
g.reset().clearRect(R);
|
||||||
|
g.setColor(0,0,0).setFont("Vector",25);
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.drawString("Benchmark", 85,35);
|
||||||
|
g.setColor(0,0,0).setFont("Vector",18);
|
||||||
|
g.drawString("Press button", 85,55);
|
||||||
|
}
|
||||||
|
function lineBench() {
|
||||||
|
/* 500 lines a second on hardware, 125 lines with flip */
|
||||||
|
for (let i=0; i<1000; i++) {
|
||||||
|
let x1 = Math.random() * 160;
|
||||||
|
let y1 = Math.random() * 160;
|
||||||
|
let x2 = Math.random() * 160;
|
||||||
|
let y2 = Math.random() * 160;
|
||||||
|
|
||||||
|
g.drawLine(x1, y1, x2, y2);
|
||||||
|
//g.flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function polyBench() {
|
||||||
|
/* 275 hollow polygons a second on hardware, 99 with flip */
|
||||||
|
/* 261 filled polygons a second on hardware, 99 with flip */
|
||||||
|
for (let i=0; i<1000; i++) {
|
||||||
|
let x1 = Math.random() * 160;
|
||||||
|
let y1 = Math.random() * 160;
|
||||||
|
let x2 = Math.random() * 160;
|
||||||
|
let y2 = Math.random() * 160;
|
||||||
|
let c = Math.random();
|
||||||
|
|
||||||
|
g.setColor(c, c, c);
|
||||||
|
g.fillPoly([80, x1, y1, 80, 80, x2, y2, 80], true);
|
||||||
|
//g.flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checksum(d) {
|
||||||
|
let sum = 0;
|
||||||
|
for (i=0; i<d.length; i++) {
|
||||||
|
sum += (d[i]*1);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
function linearRead() {
|
||||||
|
/* 10000b block -> 8.3MB/sec, 781..877 IOPS
|
||||||
|
1000b block -> 920K/sec, 909 IOPS, 0.55 sec
|
||||||
|
100b block -> 100K/sec
|
||||||
|
10b block -> 10K/sec, 1020 IOPS, 914 IOPS with ops counting
|
||||||
|
|
||||||
|
1000b block backwards -- 0.59 sec.
|
||||||
|
100b block -- 5.93.
|
||||||
|
backwards -- 6.27
|
||||||
|
random -- 7.13
|
||||||
|
checksum 5.97 -> 351 seconds with checksum. 1400bytes/second
|
||||||
|
*/
|
||||||
|
|
||||||
|
let size = 500000;
|
||||||
|
let block = 100;
|
||||||
|
let i = 0;
|
||||||
|
let ops = 0;
|
||||||
|
let sum = 0;
|
||||||
|
while (i < size) {
|
||||||
|
//let pos = Math.random() * size;
|
||||||
|
let pos = i;
|
||||||
|
//let pos = size-i;
|
||||||
|
let d = require("Storage").read("delme.mtar", pos, block);
|
||||||
|
//sum += checksum(E.toUint8Array(d));
|
||||||
|
i += block;
|
||||||
|
ops ++;
|
||||||
|
}
|
||||||
|
print(ops, "ops", sum);
|
||||||
|
}
|
||||||
|
function drawBench(name) {
|
||||||
|
g.setColor(0,0,0).setFont("Vector",25);
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.drawString(name, 85,35);
|
||||||
|
g.setColor(0,0,0).setFont("Vector",18);
|
||||||
|
g.drawString("Running", 85,55);
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
function runBench(b, name) {
|
||||||
|
drawBench(name);
|
||||||
|
g.reset().clearRect(R);
|
||||||
|
|
||||||
|
let t1 = getTime();
|
||||||
|
print("--------------------------------------------------");
|
||||||
|
print("Running",name);
|
||||||
|
b();
|
||||||
|
let m = (getTime()-t1) + " sec";
|
||||||
|
print("..done in", m);
|
||||||
|
drawBench(name);
|
||||||
|
g.setColor(0,0,0).setFont("Vector",18);
|
||||||
|
g.drawString(m, 85,85);
|
||||||
|
|
||||||
|
}
|
||||||
|
function redraw() {
|
||||||
|
//runBench(lineBench, "Lines");
|
||||||
|
runBench(polyBench, "Polygons");
|
||||||
|
//runBench(linearRead, "Linear read");
|
||||||
|
}
|
||||||
|
function showMap() {
|
||||||
|
g.reset().clearRect(R);
|
||||||
|
redraw();
|
||||||
|
emptyMap();
|
||||||
|
}
|
||||||
|
function emptyMap() {
|
||||||
|
Bangle.setUI({mode:"custom",drag:e=>{
|
||||||
|
g.reset().clearRect(R);
|
||||||
|
redraw();
|
||||||
|
}, btn: btn=>{
|
||||||
|
mapVisible = false;
|
||||||
|
var menu = {"":{title:"Benchmark"},
|
||||||
|
"< Back": ()=> showMap(),
|
||||||
|
/*LANG*/"Run": () =>{
|
||||||
|
showMap();
|
||||||
|
}};
|
||||||
|
E.showMenu(menu);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
const st = require('Storage');
|
||||||
|
const hs = require('heatshrink');
|
||||||
|
|
||||||
|
introScreen();
|
||||||
|
emptyMap();
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "spacew",
|
{ "id": "spacew",
|
||||||
"name": "Space Weaver",
|
"name": "Space Weaver",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "Application for displaying vector maps",
|
"description": "Application for displaying vector maps",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
@ -8,6 +8,7 @@
|
||||||
"tags": "outdoors,gps,osm",
|
"tags": "outdoors,gps,osm",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"spacew.app.js","url":"app.js"},
|
{"name":"spacew.app.js","url":"app.js"},
|
||||||
{"name":"spacew.img","url":"app-icon.js","evaluate":true}
|
{"name":"spacew.img","url":"app-icon.js","evaluate":true},
|
||||||
|
{"name":"world.mtar","url":"world.mtar"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#!/usr/bin/nodejs
|
#!/usr/bin/nodejs
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/49129643/how-do-i-merge-an-array-of-uint8arrays
|
||||||
|
|
||||||
var pc = 1;
|
var pc = 1;
|
||||||
var hack = 0;
|
|
||||||
const hs = require('./heatshrink.js');
|
const hs = require('./heatshrink.js');
|
||||||
|
|
||||||
if (pc) {
|
if (pc) {
|
||||||
|
@ -23,19 +24,21 @@ function writeTar(tar, dir) {
|
||||||
var h_len = 16;
|
var h_len = 16;
|
||||||
var cur = h_len;
|
var cur = h_len;
|
||||||
files = fs.readdirSync(dir);
|
files = fs.readdirSync(dir);
|
||||||
data = '';
|
let data = [];
|
||||||
var directory = '';
|
var directory = '';
|
||||||
var json = {};
|
var json = {};
|
||||||
for (f of files) {
|
for (f of files) {
|
||||||
|
let f_rec = {};
|
||||||
d = fs.readFileSync(dir+f);
|
d = fs.readFileSync(dir+f);
|
||||||
cs = d;
|
if (0) {
|
||||||
//cs = String.fromCharCode.apply(null, hs.compress(d))
|
cs = hs.compress(d);
|
||||||
|
f_rec.comp = "hs";
|
||||||
|
} else
|
||||||
|
cs = d;
|
||||||
print("Processing", f, cur, d.length, cs.length);
|
print("Processing", f, cur, d.length, cs.length);
|
||||||
//if (d.length == 42) continue;
|
data.push(cs);
|
||||||
data = data + cs;
|
|
||||||
var f_rec = {};
|
|
||||||
f_rec.st = cur;
|
f_rec.st = cur;
|
||||||
var len = d.length;
|
var len = cs.length;
|
||||||
f_rec.si = len;
|
f_rec.si = len;
|
||||||
cur = cur + len;
|
cur = cur + len;
|
||||||
json[f] = f_rec;
|
json[f] = f_rec;
|
||||||
|
@ -53,10 +56,10 @@ function writeTar(tar, dir) {
|
||||||
while (header.length < h_len) {
|
while (header.length < h_len) {
|
||||||
header = header+' ';
|
header = header+' ';
|
||||||
}
|
}
|
||||||
if (!hack)
|
fs.writeFileSync(tar, header);
|
||||||
fs.writeFileSync(tar, header+data+directory);
|
for (d of data)
|
||||||
else
|
fs.appendFileSync(tar, Buffer.from(d));
|
||||||
fs.writeFileSync(tar, directory);
|
fs.appendFileSync(tar, directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readTarFile(tar, f) {
|
function readTarFile(tar, f) {
|
||||||
|
@ -70,7 +73,7 @@ function readTarFile(tar, f) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pc)
|
if (pc)
|
||||||
writeTar("delme.mtaz", "delme/");
|
writeTar("delme.mtar", "delme/");
|
||||||
else {
|
else {
|
||||||
print(readTarFile("delme.mtar", "ahoj"));
|
print(readTarFile("delme.mtar", "ahoj"));
|
||||||
print(readTarFile("delme.mtar", "nazdar"));
|
print(readTarFile("delme.mtar", "nazdar"));
|
||||||
|
|
|
@ -14,5 +14,5 @@
|
||||||
"linear_tags": true,
|
"linear_tags": true,
|
||||||
"area_tags": false,
|
"area_tags": false,
|
||||||
"exclude_tags": [],
|
"exclude_tags": [],
|
||||||
"include_tags": [ "place", "name", "landuse", "highway" ]
|
"include_tags": [ "place", "name", "landuse", "highway", "natural" ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,49 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
if [ ".$1" == "-f" ]; then
|
|
||||||
|
# http://bboxfinder.com/#0.000000,0.000000,0.000000,0.000000
|
||||||
|
Z=
|
||||||
|
# Czech republic -- hitting internal limit in nodejs
|
||||||
|
#BBOX=10,60,20,30
|
||||||
|
#Z="--maxz 9"
|
||||||
|
# No Moravia -- ascii conversion takes 43min, "Error: Cannot create a string longer than 0x3fffffe7 characters"
|
||||||
|
#BBOX=10,60,17.75,30
|
||||||
|
#Z="--maxz 9"
|
||||||
|
# Just Moravia -- 266MB delme.pbf -- FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed
|
||||||
|
#BBOX=16.13,60,35,30
|
||||||
|
#Z="--maxz 9"
|
||||||
|
# Roudnice az Kutna hora -- band 1.1 deg -- 145MB delme.pbf, 5m cstocs, 40+m split.
|
||||||
|
# -- band 0.1 deg -- 13MB delme.pbf, 13s split, 21k result.
|
||||||
|
#BBOX=14.20,50.45,15.32,49.20
|
||||||
|
#Z="--maxz 9"
|
||||||
|
# Roudnice az... -- band 0.5 deg -- 91MB delme.pbf, 200k result.
|
||||||
|
# Roudnice az... -- band 0.8 deg -- 120MB delme.pbf, 2.5GB while splitting, 260k result.
|
||||||
|
#BBOX=14.20,50.45,15.0,49.20
|
||||||
|
#Z="--maxz 9"
|
||||||
|
# Prague; 1.2MB map, not really useful
|
||||||
|
#BBOX=14.25,50.17,14.61,49.97
|
||||||
|
#Z="--maxz 14"
|
||||||
|
# Zernovka small -- 3.5 delme.pbf, ~850K result.
|
||||||
|
BBOX=14.7,49.9,14.8,50.1
|
||||||
|
# Zernovka big
|
||||||
|
#BBOX=14.6,49.7,14.9,50.1
|
||||||
|
|
||||||
|
if [ ".$1" == ".-f" ]; then
|
||||||
I=/data/gis/osm/dumps/czech_republic-2023-07-24.osm.pbf
|
I=/data/gis/osm/dumps/czech_republic-2023-07-24.osm.pbf
|
||||||
#I=/data/gis/osm/dumps/zernovka.osm.bz2
|
#I=/data/gis/osm/dumps/zernovka.osm.bz2
|
||||||
O=cr.geojson
|
O=cr.geojson
|
||||||
rm delme.pbf $O
|
rm delme.pbf $O
|
||||||
time osmium extract $I --bbox 14.7,49.9,14.8,50.1 -f pbf -o delme.pbf
|
ls -alh $I
|
||||||
|
time osmium extract $I --bbox $BBOX -f pbf -o delme.pbf
|
||||||
|
ls -alh delme.pbf
|
||||||
time osmium export delme.pbf -c prepare.json -o $O
|
time osmium export delme.pbf -c prepare.json -o $O
|
||||||
|
ls -alh $O
|
||||||
|
# ~.5G in 15min
|
||||||
echo "Converting to ascii"
|
echo "Converting to ascii"
|
||||||
time cstocs utf8 ascii cr.geojson > cr_ascii.geojson
|
time cstocs utf8 ascii cr.geojson > cr_ascii.geojson
|
||||||
mv -f cr_ascii.geojson delme.json
|
mv -f cr_ascii.geojson delme.json
|
||||||
fi
|
fi
|
||||||
rm -r delme/; mkdir delme
|
rm -r delme/; mkdir delme
|
||||||
./split.js
|
time ./split.js $Z
|
||||||
./minitar.js
|
./minitar.js
|
||||||
ls -lS delme/*.json | head -20
|
ls -lS delme/*.json | head -20
|
||||||
cat delme/* | wc -c
|
cat delme/* | wc -c
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const sphm = require('./sphericalmercator.js');
|
const sphm = require('./sphericalmercator.js');
|
||||||
var split = require('geojson-vt')
|
var split = require('geojson-vt');
|
||||||
|
const process = require('process');
|
||||||
|
|
||||||
|
|
||||||
// delme.json needs to be real file, symlink to geojson will not work
|
// delme.json needs to be real file, symlink to geojson will not work
|
||||||
console.log("Loading json");
|
|
||||||
var gjs = require("./delme.json");
|
|
||||||
|
|
||||||
function tileToLatLon(x, y, z, x_, y_) {
|
function tileToLatLon(x, y, z, x_, y_) {
|
||||||
var [ w, s, e, n ] = merc.bbox(x, y, z);
|
var [ w, s, e, n ] = merc.bbox(x, y, z);
|
||||||
|
@ -30,9 +30,39 @@ function convGeom(tile, geom) {
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clamp(i) {
|
||||||
|
if (i<0)
|
||||||
|
return 0;
|
||||||
|
if (i>4095)
|
||||||
|
return 4095;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function binGeom(tile, geom) {
|
||||||
|
let off = 1;
|
||||||
|
let r = new Uint8Array(geom.length * 3 + off);
|
||||||
|
let j = off;
|
||||||
|
for (i = 0; i< geom.length; i++) {
|
||||||
|
let x = geom[i][0];
|
||||||
|
let y = geom[i][1];
|
||||||
|
x = clamp(x);
|
||||||
|
y = clamp(y);
|
||||||
|
r[j++] = x >> 4;
|
||||||
|
r[j++] = y >> 4;
|
||||||
|
r[j++] = (x & 0x0f) + ((y & 0x0f) << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
function zoomPoint(tags) {
|
function zoomPoint(tags) {
|
||||||
var z = 99;
|
var z = 99;
|
||||||
|
|
||||||
|
if (tags.featurecla == "Admin-0 scale ranksscalerank") z = 2;
|
||||||
|
if (tags.featurecla == "Admin-0 capital") z = 3;
|
||||||
|
if (tags.featurecla == "Admin-1 capital") z = 4;
|
||||||
|
if (tags.featurecla == "Populated place") z = 5;
|
||||||
|
|
||||||
if (tags.place == "city") z = 4;
|
if (tags.place == "city") z = 4;
|
||||||
if (tags.place == "town") z = 8;
|
if (tags.place == "town") z = 8;
|
||||||
if (tags.place == "village") z = 10;
|
if (tags.place == "village") z = 10;
|
||||||
|
@ -40,10 +70,59 @@ function zoomPoint(tags) {
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var meta = {};
|
||||||
|
var ac = -1;
|
||||||
|
meta.attrs = [];
|
||||||
|
var a_town = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 1;
|
||||||
|
var a_village = ++ac
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 1;
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties["marker-color"] = "#800000";
|
||||||
|
var a_way = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 2;
|
||||||
|
var a_secondary = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 2;
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties.stroke = "#000040";
|
||||||
|
var a_tertiary = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 2;
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties.stroke = "#000080";
|
||||||
|
var a_track = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 2;
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties.stroke = "#404040";
|
||||||
|
var a_path = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 2;
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties.stroke = "#408040";
|
||||||
|
var a_polygon = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].type = 3;
|
||||||
|
var a_forest = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties.fill = "#c0ffc0";
|
||||||
|
meta.attrs[ac].type = 3;
|
||||||
|
var a_water = ++ac;
|
||||||
|
meta.attrs[ac] = {};
|
||||||
|
meta.attrs[ac].properties = {};
|
||||||
|
meta.attrs[ac].properties.fill = "#c0c0ff";
|
||||||
|
meta.attrs[ac].type = 3;
|
||||||
|
|
||||||
function paintPoint(tags) {
|
function paintPoint(tags) {
|
||||||
var p = {};
|
var p = {};
|
||||||
|
|
||||||
if (tags.place == "village") p["marker-color"] = "#ff0000";
|
if (tags.place == "city" || tags.place == "town") { p.attr = a_town; }
|
||||||
|
if (tags.place == "village") { p.attr = a_village; p["marker-color"] = "#ff0000"; }
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -51,15 +130,17 @@ function paintPoint(tags) {
|
||||||
function zoomWay(tags) {
|
function zoomWay(tags) {
|
||||||
var z = 99;
|
var z = 99;
|
||||||
|
|
||||||
|
if (tags.scalerank == 0) z = 0;
|
||||||
|
|
||||||
if (tags.highway == "motorway") z = 7;
|
if (tags.highway == "motorway") z = 7;
|
||||||
if (tags.highway == "primary") z = 9;
|
if (tags.highway == "primary") z = 9;
|
||||||
if (tags.highway == "secondary") z = 13;
|
if (tags.highway == "secondary") z = 13;
|
||||||
if (tags.highway == "tertiary") z = 14;
|
if (tags.highway == "tertiary") z = 14;
|
||||||
if (tags.highway == "unclassified") z = 16;
|
if (tags.highway == "unclassified") z = 15;
|
||||||
if (tags.highway == "residential") z = 17;
|
if (tags.highway == "residential") z = 15;
|
||||||
if (tags.highway == "track") z = 17;
|
if (tags.highway == "track") z = 15;
|
||||||
if (tags.highway == "path") z = 17;
|
if (tags.highway == "path") z = 16;
|
||||||
if (tags.highway == "footway") z = 17;
|
if (tags.highway == "footway") z = 16;
|
||||||
|
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
|
@ -67,52 +148,120 @@ function zoomWay(tags) {
|
||||||
function paintWay(tags) {
|
function paintWay(tags) {
|
||||||
var p = {};
|
var p = {};
|
||||||
|
|
||||||
|
p.attr = a_way;
|
||||||
if (tags.highway == "motorway" || tags.highway == "primary") /* ok */;
|
if (tags.highway == "motorway" || tags.highway == "primary") /* ok */;
|
||||||
if (tags.highway == "secondary" || tags.highway == "tertiary") p.stroke = "#0000ff";
|
if (tags.highway == "secondary" || tags.highway == "tertiary") { p.stroke = "#0000ff"; p.attr = a_secondary; }
|
||||||
if (tags.highway == "tertiary" || tags.highway == "unclassified" || tags.highway == "residential") p.stroke = "#00ff00";
|
if (tags.highway == "tertiary" || tags.highway == "unclassified" || tags.highway == "residential") { p.stroke = "#00ff00"; p.attr = a_tertiary; }
|
||||||
if (tags.highway == "track") p.stroke = "#ff0000";
|
if (tags.highway == "track") { p.stroke = "#ff0000"; p.attr = a_track; }
|
||||||
if (tags.highway == "path" || tags.highway == "footway") p.stroke = "#800000";
|
if (tags.highway == "path" || tags.highway == "footway") { p.stroke = "#800000"; p.attr = a_path; }
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomPolygon(tags) {
|
||||||
|
var z = 99;
|
||||||
|
|
||||||
|
if (tags.scalerank == 0) z = 0;
|
||||||
|
|
||||||
|
if (tags.landuse == "forest") z = 16;
|
||||||
|
if (tags.natural == "water") z = 16;
|
||||||
|
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintPolygon(tags) {
|
||||||
|
var p = {};
|
||||||
|
|
||||||
|
p.attr = a_polygon;
|
||||||
|
if (tags.landuse == "forest") { p.fill = "#c0ffc0"; p.attr = a_forest; }
|
||||||
|
if (tags.natural == "water") { p.fill = "#c0c0ff"; p.attr = a_water; }
|
||||||
|
|
||||||
|
if (tags.featurecla == "Admin-0 sovereignty") p.attr = a_way;
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeFeatures(name, feat)
|
function writeFeatures(name, feat)
|
||||||
{
|
{
|
||||||
var n = {};
|
if (0) {
|
||||||
n.type = "FeatureCollection";
|
var n = {};
|
||||||
n.features = feat;
|
n.type = "FeatureCollection";
|
||||||
|
n.features = feat;
|
||||||
|
|
||||||
fs.writeFile(name+'.json', JSON.stringify(n), on_error);
|
fs.writeFile(name+'.json', JSON.stringify(n), on_error);
|
||||||
|
} else {
|
||||||
|
if (feat.length > 0)
|
||||||
|
fs.writeFile(name+'.json', JSON.stringify(feat), on_error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function btoa(s) {
|
||||||
|
return Buffer.from(s).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
// E.toString()
|
||||||
|
|
||||||
function toGjson(name, d, tile) {
|
function toGjson(name, d, tile) {
|
||||||
var cnt = 0;
|
var cnt = 0;
|
||||||
var feat = [];
|
var feat = [];
|
||||||
for (var a of d) {
|
for (var a of d) {
|
||||||
var f = {};
|
let f = {}; // geojson output
|
||||||
|
let b = {}; // moving towards binary output
|
||||||
var zoom = 99;
|
var zoom = 99;
|
||||||
var p = {};
|
var p = {};
|
||||||
|
var bin = [];
|
||||||
|
if (!a.tags)
|
||||||
|
a.tags = a.properties;
|
||||||
f.properties = a.tags;
|
f.properties = a.tags;
|
||||||
f.type = "Feature";
|
f.type = "Feature";
|
||||||
f.geometry = {};
|
f.geometry = {};
|
||||||
if (a.type == 1) {
|
if (a.type == 1) {
|
||||||
f.geometry.type = "Point";
|
f.geometry.type = "Point";
|
||||||
f.geometry.coordinates = convGeom(tile, a.geometry)[0];
|
f.geometry.coordinates = convGeom(tile, a.geometry)[0];
|
||||||
|
bin = binGeom(tile, a.geometry);
|
||||||
zoom = zoomPoint(a.tags);
|
zoom = zoomPoint(a.tags);
|
||||||
p = paintPoint(a.tags);
|
p = paintPoint(a.tags);
|
||||||
} else if (a.type == 2) {
|
} else if (a.type == 2) {
|
||||||
f.geometry.type = "LineString";
|
f.geometry.type = "LineString";
|
||||||
f.geometry.coordinates = convGeom(tile, a.geometry[0]);
|
f.geometry.coordinates = convGeom(tile, a.geometry[0]);
|
||||||
|
bin = binGeom(tile, a.geometry[0]);
|
||||||
zoom = zoomWay(a.tags);
|
zoom = zoomWay(a.tags);
|
||||||
p = paintWay(a.tags);
|
p = paintWay(a.tags);
|
||||||
|
if (zoom == 99) {
|
||||||
|
f.geometry.type = "Polygon";
|
||||||
|
zoom = zoomPolygon(a.tags);
|
||||||
|
p = paintPolygon(a.tags);
|
||||||
|
}
|
||||||
|
} else if (a.type == 3) {
|
||||||
|
f.geometry.type = "Polygon";
|
||||||
|
f.geometry.coordinates = convGeom(tile, a.geometry[0]);
|
||||||
|
bin = binGeom(tile, a.geometry[0]);
|
||||||
|
zoom = zoomPolygon(a.tags);
|
||||||
|
p = paintPolygon(a.tags);
|
||||||
} else {
|
} else {
|
||||||
//console.log("Unknown type", a.type);
|
console.log("Unknown type", a.type);
|
||||||
}
|
}
|
||||||
//zoom -= 4; // Produces way nicer map, at expense of space.
|
//zoom -= 4; // Produces way nicer map, at expense of space.
|
||||||
if (tile.z < zoom)
|
if (tile.z < zoom)
|
||||||
continue;
|
continue;
|
||||||
f.properties = Object.assign({}, f.properties, p);
|
f.properties = Object.assign({}, f.properties, p);
|
||||||
feat.push(f);
|
//feat.push(f); FIXME
|
||||||
|
bin[0] = p.attr;
|
||||||
|
b.b = btoa(bin);
|
||||||
|
b.tags = {};
|
||||||
|
if (a.tags.name)
|
||||||
|
b.tags.name = a.tags.name;
|
||||||
|
if (a.tags.nameascii)
|
||||||
|
b.tags.name = a.tags.nameascii;
|
||||||
|
if (a.tags.sr_subunit)
|
||||||
|
b.tags.name = a.tags.sr_subunit;
|
||||||
|
|
||||||
|
//delete(a.tags.highway);
|
||||||
|
//delete(a.tags.landuse);
|
||||||
|
//delete(a.tags.natural);
|
||||||
|
//delete(a.tags.place);
|
||||||
|
// b.properties = p
|
||||||
|
feat.push(b);
|
||||||
var s = JSON.stringify(feat);
|
var s = JSON.stringify(feat);
|
||||||
if (s.length > 6000) {
|
if (s.length > 6000) {
|
||||||
console.log("tile too big, splitting", cnt);
|
console.log("tile too big, splitting", cnt);
|
||||||
|
@ -135,16 +284,41 @@ var merc = new sphm({
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Splitting data");
|
console.log("Splitting data");
|
||||||
var meta = {}
|
|
||||||
meta.min_zoom = 0;
|
meta.min_zoom = 0;
|
||||||
meta.max_zoom = 17; // HERE
|
meta.max_zoom = 16; // HERE
|
||||||
// = 16 ... split3 takes > 30 minutes
|
// = 16 ... split3 takes > 30 minutes
|
||||||
// = 13 ... 2 minutes
|
// = 13 ... 2 minutes
|
||||||
|
if (process.argv[2] == "-h") {
|
||||||
|
console.log("help here");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
if (process.argv[2] == "--maxz") {
|
||||||
|
meta.max_zoom = 1*process.argv[3];
|
||||||
|
console.log("... max zoom", meta.max_zoom);
|
||||||
|
}
|
||||||
|
if (process.argv[2] == "--world") {
|
||||||
|
console.log("Loading world");
|
||||||
|
meta.max_zoom = 4;
|
||||||
|
var g_sovereign = require("./ne_10m_admin_0_sovereignty.json");
|
||||||
|
var g_labels = require("./ne_10m_admin_0_label_points.json");
|
||||||
|
var g_places = require("./ne_10m_populated_places_simple.json");
|
||||||
|
|
||||||
|
gjs = {}
|
||||||
|
gjs.type = "FeatureCollection";
|
||||||
|
//gjs.features = g_sovereign.features + g_labels.features + g_places.features;
|
||||||
|
gjs.features = g_sovereign.features.concat(g_labels.features).concat(g_places.features);
|
||||||
|
console.log(gjs);
|
||||||
|
} else {
|
||||||
|
console.log("Loading json");
|
||||||
|
gjs = require("./delme.json");
|
||||||
|
}
|
||||||
|
|
||||||
var index = split(gjs, Object.assign({
|
var index = split(gjs, Object.assign({
|
||||||
maxZoom: meta.max_zoom,
|
maxZoom: meta.max_zoom,
|
||||||
indexMaxZoom: meta.max_zoom,
|
indexMaxZoom: meta.max_zoom,
|
||||||
indexMaxPoints: 0,
|
indexMaxPoints: 0,
|
||||||
tolerance: 30,
|
tolerance: 10,
|
||||||
|
buffer: 0,
|
||||||
}), {});
|
}), {});
|
||||||
console.log("Producing output");
|
console.log("Producing output");
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
zoom() {
|
zoom() {
|
||||||
echo "Zoom $1"
|
VAL=`cat delme/z$1-* | wc -c`
|
||||||
cat delme/z$1-* | wc -c
|
echo "Zoom $1 -- " $[$VAL/1024]
|
||||||
echo "M..k..."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Total data"
|
echo "Total data"
|
||||||
|
@ -16,7 +15,17 @@ zoom 14
|
||||||
zoom 13
|
zoom 13
|
||||||
zoom 12
|
zoom 12
|
||||||
zoom 11
|
zoom 11
|
||||||
zoom 10
|
zoom 9
|
||||||
|
zoom 8
|
||||||
|
zoom 7
|
||||||
|
zoom 6
|
||||||
|
zoom 5
|
||||||
|
zoom 4
|
||||||
|
zoom 3
|
||||||
|
zoom 2
|
||||||
|
zoom 1
|
||||||
|
zoom 0
|
||||||
|
|
||||||
echo "Zoom 1..9"
|
echo "Zoom 1..9"
|
||||||
cat delme/z?-* | wc -c
|
cat delme/z?-* | wc -c
|
||||||
echo "M..k..."
|
echo "M..k..."
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
if [ ".$1" == ".-f" ]; then
|
||||||
|
wget https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/10m/cultural/ne_10m_admin_0_sovereignty.json
|
||||||
|
wget https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/10m/cultural/ne_10m_admin_0_label_points.json
|
||||||
|
wget https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/10m/cultural/ne_10m_populated_places_simple.json
|
||||||
|
fi
|
||||||
|
rm delme.json
|
||||||
|
rm -r delme/; mkdir delme
|
||||||
|
./split.js --world
|
||||||
|
./minitar.js
|
||||||
|
ls -lS delme/*.json | head -20
|
||||||
|
cat delme/* | wc -c
|
||||||
|
ls -l delme.mtar
|
||||||
|
|
|
@ -75,7 +75,7 @@ function getLapTimes() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (task=="download") {
|
if (task=="download") {
|
||||||
Util.saveCSV(lap.n.slice(0,-5)+".csv", lap.d.map((d,n)=>[n+1,d].join(",")).join("\n"));
|
Util.saveCSV(lap.n.slice(0,-5), lap.d.map((d,n)=>[n+1,d].join(",")).join("\n"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ function getData() {
|
||||||
<th>Month</th>
|
<th>Month</th>
|
||||||
<th>Day</th>
|
<th>Day</th>
|
||||||
<th>Time HH:mm</th>
|
<th>Time HH:mm</th>
|
||||||
<th>temperature</th>
|
<th>temperature</th>
|
||||||
</tr>`+data.trim().split("\n").map(l=>{
|
</tr>`+data.trim().split("\n").map(l=>{
|
||||||
l = l.split(";");
|
l = l.split(";");
|
||||||
return `<tr>
|
return `<tr>
|
||||||
|
@ -47,7 +47,7 @@ function getData() {
|
||||||
|
|
||||||
// You can call a utility function to save the data
|
// You can call a utility function to save the data
|
||||||
document.getElementById("btnSave").addEventListener("click", function() {
|
document.getElementById("btnSave").addEventListener("click", function() {
|
||||||
Util.saveCSV("temphistory.csv", csvData);
|
Util.saveCSV("temphistory", csvData);
|
||||||
});
|
});
|
||||||
// Or you can also delete the file
|
// Or you can also delete the file
|
||||||
document.getElementById("btnDelete").addEventListener("click", function() {
|
document.getElementById("btnDelete").addEventListener("click", function() {
|
||||||
|
|
|
@ -95,17 +95,7 @@
|
||||||
Util.showModal("Downloading...");
|
Util.showModal("Downloading...");
|
||||||
let jsonTodos = getEditableContent();
|
let jsonTodos = getEditableContent();
|
||||||
if (isJsonString(jsonTodos)) {
|
if (isJsonString(jsonTodos)) {
|
||||||
var a = document.createElement("a"),
|
Util.saveFile(fileTodoList, "application/json", jsonTodos);
|
||||||
file = new Blob([jsonTodos], { type: "application/json" });
|
|
||||||
var url = URL.createObjectURL(file);
|
|
||||||
a.href = url;
|
|
||||||
a.download = fileTodoList;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
setTimeout(function () {
|
|
||||||
document.body.removeChild(a);
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}, 0);
|
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("info").innerHTML = errorFormat();
|
document.getElementById("info").innerHTML = errorFormat();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,3 +8,4 @@
|
||||||
0.09: Misc speed/memory tweaks
|
0.09: Misc speed/memory tweaks
|
||||||
0.10: Color changes due to the battery level
|
0.10: Color changes due to the battery level
|
||||||
0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible
|
0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible
|
||||||
|
0.12: Use black flash instead of green fork to indicate charging
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "widbat",
|
"id": "widbat",
|
||||||
"name": "Battery Level Widget",
|
"name": "Battery Level Widget",
|
||||||
"version": "0.11",
|
"version": "0.12",
|
||||||
"description": "Show the current battery level and charging status in the top right of the clock",
|
"description": "Show the current battery level and charging status in the top right of the clock",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
(function(){
|
{
|
||||||
function setWidth() {
|
|
||||||
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
|
|
||||||
}
|
|
||||||
Bangle.on('charging',function(charging) {
|
Bangle.on('charging',function(charging) {
|
||||||
if(charging) Bangle.buzz();
|
if(charging) Bangle.buzz();
|
||||||
setWidth();
|
WIDGETS["bat"].draw();
|
||||||
Bangle.drawWidgets(); // re-layout widgets
|
|
||||||
g.flip();
|
g.flip();
|
||||||
});
|
});
|
||||||
var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined;
|
let batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined;
|
||||||
Bangle.on('lcdPower', function(on) {
|
Bangle.on('lcdPower', function(on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
WIDGETS["bat"].draw();
|
WIDGETS["bat"].draw();
|
||||||
|
@ -23,19 +19,14 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
WIDGETS["bat"]={area:"tr",width:40,draw:function() {
|
WIDGETS["bat"]={area:"tr",width:40,draw:function() {
|
||||||
var s = 39;
|
|
||||||
var x = this.x, y = this.y;
|
var x = this.x, y = this.y;
|
||||||
g.reset();
|
g.reset().setColor(g.theme.fg).fillRect(x,y+2,x+35,y+21).clearRect(x+2,y+4,x+33,y+19).fillRect(x+36,y+10,x+39,y+14);
|
||||||
if (Bangle.isCharging()) {
|
|
||||||
g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
|
||||||
x+=16;
|
|
||||||
}
|
|
||||||
g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14);
|
|
||||||
var battery = E.getBattery();
|
var battery = E.getBattery();
|
||||||
if(battery < 20) {g.setColor("#f00");}
|
if(battery < 20) g.setColor("#f00");
|
||||||
else if (battery < 40) {g.setColor(g.theme.dark ? "#ff0" : "#f80");}
|
else if (battery < 40) g.setColor(g.theme.dark ? "#ff0" : "#f80");
|
||||||
else {g.setColor("#0f0");}
|
else g.setColor("#0f0");
|
||||||
g.fillRect(x+4,y+6,x+4+battery*(s-12)/100,y+17);
|
g.fillRect(x+4,y+6,x+4+battery*27/100,y+17);
|
||||||
|
if (Bangle.isCharging())
|
||||||
|
g.reset().drawImage(atob("FAqBAAHAAA8AAPwAB/D4f8P+Hw/gAD8AAPAAA4A="),x+8,y+7);
|
||||||
}};
|
}};
|
||||||
setWidth();
|
}
|
||||||
})()
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"id": "widcasiologo",
|
||||||
|
"name": "Casio Logo Widget",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "A simple widget that displays Casio logo when clock screen is active.",
|
||||||
|
"icon": "widget.png",
|
||||||
|
"type": "widget",
|
||||||
|
"tags": "widget,clock",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"widcasiologo.wid.js","url":"widget.js"}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
WIDGETS["casiologo"]={
|
||||||
|
area:"tl",
|
||||||
|
width: Bangle.CLOCK?70:0,
|
||||||
|
draw: function () {
|
||||||
|
if (!Bangle.CLOCK != !this.width) { // if we're the wrong size for if we have a clock or not...
|
||||||
|
this.width = Bangle.CLOCK?70:0;
|
||||||
|
return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw
|
||||||
|
}
|
||||||
|
if (!this.width) return;
|
||||||
|
g.reset().setColor(g.theme.dark?"#fff":"#000").drawImage(
|
||||||
|
atob("OgwCAAAAAAAAAAAAAAAAAAAA///wA//AD//8D8D///A////AP/wD///w/D///8P9B/wL//A/AD8Pw/wC/D8AD8D8PwPwAAD8PwAPw/AAAA/D8D///A/D8AD8PwAAA/gPwP//8Pw/AA/D8AD8P//8AAA/T8PwAPw/wD/P///z8AP0/D/Af8P///z/AL8///8Pw////A///w/AA/T///D8D///AAAAAAAAAAAAAAAAAAAA"),
|
||||||
|
this.x+10,
|
||||||
|
this.y+6)
|
||||||
|
}
|
||||||
|
};
|
After Width: | Height: | Size: 1006 B |
|
@ -0,0 +1 @@
|
||||||
|
0.01: First release
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Last announced heartrate BPM Widget
|
||||||
|
|
||||||
|
* Displays the last announced bpm measurement from Bangle.on('HRM', ...);
|
||||||
|
* it does not enable the heartrate sensor to do measurements it waits on such annoucement. I use it to view the last read value when letting the system take a reading every 10 minutes.
|
||||||
|
* saves last read value to a file so it can display that last value between app starts / reboots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Code based on Lato Pedometer Written by: [Hugh Barney](https://github.com/hughbarney)
|
||||||
|
Code Modified by: [Willems Davy](https://github.com/joyrider3774)
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"id": "widhr",
|
||||||
|
"name": "Last announced heartrate BPM Widget",
|
||||||
|
"shortName":"Last Heartrate BPM",
|
||||||
|
"icon": "widhr.icon.png",
|
||||||
|
"screenshots": [{"url":"screenshot_widhr.png"}],
|
||||||
|
"version":"0.01",
|
||||||
|
"type": "widget",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"description": "Displays the last announced heartrate BPM from `Bangle.on(\"HRM\", ...);` (it does not enable the hrm sensor it expects the system or other app todo so)",
|
||||||
|
"tags": "widget",
|
||||||
|
"data": [
|
||||||
|
{"name":"widhr.data.json"}
|
||||||
|
],
|
||||||
|
"storage": [
|
||||||
|
{"name":"widhr.wid.js","url":"widhr.wid.js"}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.1 KiB |
|
@ -0,0 +1,30 @@
|
||||||
|
function widhr_hrm(hrm) {
|
||||||
|
require("Storage").writeJSON("widhr.data.json", {"bpm":hrm.bpm});
|
||||||
|
WIDGETS["widhr"].draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('HRM', widhr_hrm);
|
||||||
|
|
||||||
|
function widhr_draw() {
|
||||||
|
var json = require("Storage").readJSON("widhr.data.json");
|
||||||
|
var bpm = json === undefined ? 0 : json.bpm;
|
||||||
|
//3x6 from bpm text in 6x8 font
|
||||||
|
var w = (bpm.toString().length)*8 > 3 * 6 ? (bpm.toString().length)*8 : 3 * 6;
|
||||||
|
if (w != this.width)
|
||||||
|
{
|
||||||
|
this.width = w;
|
||||||
|
setTimeout(() => Bangle.drawWidgets(),10); return;
|
||||||
|
}
|
||||||
|
g.reset();
|
||||||
|
g.setColor(g.theme.bg);
|
||||||
|
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.setFont("6x8:1");
|
||||||
|
g.setFontAlign(-1, 0);
|
||||||
|
g.drawString("bpm", this.x, this.y + 5);
|
||||||
|
g.setFont("4x6:2");
|
||||||
|
g.setFontAlign(-1, 0);
|
||||||
|
g.drawString(bpm, this.x, this.y + 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
WIDGETS["widhr"]={area:"tl",sortorder:-1,width:13,draw:widhr_draw};
|
2
core
|
@ -1 +1 @@
|
||||||
Subproject commit 431a3fb743da5c370729ab748cb2c177e70a345b
|
Subproject commit 71813fe2eaf19987cec07db850ab9d1959694f96
|
|
@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") {
|
||||||
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
|
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
|
||||||
}
|
}
|
||||||
|
|
||||||
var RECOMMENDED_VERSION = "2v18";
|
var RECOMMENDED_VERSION = "2v19";
|
||||||
// could check http://www.espruino.com/json/BANGLEJS.json for this
|
// could check http://www.espruino.com/json/BANGLEJS.json for this
|
||||||
|
|
||||||
// We're only interested in Bangles
|
// We're only interested in Bangles
|
||||||
|
@ -236,7 +236,7 @@ window.addEventListener('load', (event) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Button to install all default apps in one go
|
// Button to install all default apps in one go
|
||||||
el = document.getElementById("installdefault");
|
el = document.getElementById("installdefault");
|
||||||
if (el) el.addEventListener("click", event=>{
|
if (el) el.addEventListener("click", event=>{
|
||||||
|
@ -262,7 +262,7 @@ window.addEventListener('load', (event) => {
|
||||||
showToast("Settings reset!", "success");
|
showToast("Settings reset!", "success");
|
||||||
}, function() { /* cancelled */ });
|
}, function() { /* cancelled */ });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// BLE Compatibility
|
// BLE Compatibility
|
||||||
var selectBLECompat = document.getElementById("settings-ble-compat");
|
var selectBLECompat = document.getElementById("settings-ble-compat");
|
||||||
|
|
|
@ -4,11 +4,11 @@ h,b,f,a){var e=null==d.bgCol?a:g.toColor(d.bgCol);if(e!=a||"txt"==d.type||"btn"=
|
||||||
if(void 0===h&&this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);!this.options.back&&!this.options.remove||d||Bangle.setUI({mode:"custom",back:this.options.back,remove:this.options.remove});if(this.b){function h(b,f){.75<f.time-f.lastTime&&this.b[b].cbl?this.b[b].cbl(f):this.b[b].cb&&this.b[b].cb(f)}Bangle.btnWatches&&
|
if(void 0===h&&this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);!this.options.back&&!this.options.remove||d||Bangle.setUI({mode:"custom",back:this.options.back,remove:this.options.remove});if(this.b){function h(b,f){.75<f.time-f.lastTime&&this.b[b].cbl?this.b[b].cbl(f):this.b[b].cb&&this.b[b].cb(f)}Bangle.btnWatches&&
|
||||||
Bangle.btnWatches.forEach(clearWatch);Bangle.btnWatches=[];this.b[0]&&Bangle.btnWatches.push(setWatch(h.bind(this,0),BTN1,{repeat:!0,edge:-1}));this.b[1]&&Bangle.btnWatches.push(setWatch(h.bind(this,1),BTN2,{repeat:!0,edge:-1}));this.b[2]&&Bangle.btnWatches.push(setWatch(h.bind(this,2),BTN3,{repeat:!0,edge:-1}))}if(2==process.env.HWVERSION){function h(b,f){b.cb&&f.x>=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler=
|
Bangle.btnWatches.forEach(clearWatch);Bangle.btnWatches=[];this.b[0]&&Bangle.btnWatches.push(setWatch(h.bind(this,0),BTN1,{repeat:!0,edge:-1}));this.b[1]&&Bangle.btnWatches.push(setWatch(h.bind(this,1),BTN2,{repeat:!0,edge:-1}));this.b[2]&&Bangle.btnWatches.push(setWatch(h.bind(this,2),BTN3,{repeat:!0,edge:-1}))}if(2==process.env.HWVERSION){function h(b,f){b.cb&&f.x>=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler=
|
||||||
(b,f)=>h(this._l,f);Bangle.on("touch",Bangle.touchHandler)}};p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>>
|
(b,f)=>h(this._l,f);Bangle.on("touch",Bangle.touchHandler)}};p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>>
|
||||||
1),n)}else b.setFont(c.font).setFontAlign(0,0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=c.selected?b.theme.bgH:b.theme.bg2;b.setColor(n).fillPoly(m).setColor(c.selected?b.theme.fgH:b.theme.fg2).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==typeof c.src?c.src():c.src,c.x+
|
1),n)}else b.setFont(c.font).setFontAlign(0,0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=void 0!==c.bgCol?c.bgCol:b.theme.bg2;q=void 0!==c.btnBorder?c.btnBorder:b.theme.fg2;c.selected&&(n=b.theme.bgH,q=b.theme.fgH);b.setColor(n).fillPoly(m).setColor(q).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==
|
||||||
c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)}):b.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),e=[];t(d,a,e,this.rects,
|
typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)}):b.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),
|
||||||
null);for(var l in a)delete this.rects[l];d=Object.keys(a).map(c=>a[c]).reverse();for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-(b.pad<<1):k._h);k.y=0|
|
e=[];t(d,a,e,this.rects,null);for(var l in a)delete this.rects[l];d=Object.keys(a).map(c=>a[c]).reverse();for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-
|
||||||
b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<<1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram";var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};if(h[d.type])h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);h=h||1;g.setColor(h&
|
(b.pad<<1):k._h);k.y=0|b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<<1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram";var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};if(h[d.type])h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);
|
||||||
1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h);d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad,d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));
|
h=h||1;g.setColor(h&1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h);d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad,d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,
|
||||||
if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label);a._w=e.width;a._h=e.height}},btn:function(a){"ram";a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*l},
|
-1)/100));if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label);a._w=e.width;a._h=e.height}},btn:function(a){"ram";a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*
|
||||||
"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0},h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=this._l;
|
l},"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0},h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=
|
||||||
d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h,f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w,f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p
|
this._l;d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h,f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w,f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p
|