Merge pull request #1674 from berkenbu/master

New app "OpenWind"
master
Gordon Williams 2022-04-19 09:49:51 +01:00 committed by GitHub
commit 1e949f7506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 2 deletions

View File

@ -17,7 +17,7 @@ class TwoK {
bh = Math.floor(h/4);
bw = Math.floor(w/4);
g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0);
g.setFont("Vector", 16).setColor("#fff").drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
g.setFont("Vector", 16).setColor(g.theme.fg).drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false);
for (y=0; y<4; ++y)
for (x=0; x<4; ++x) {

2
apps/2047pp/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New app!
0.02: Better support for watch themes

View File

@ -2,7 +2,7 @@
"name": "2047pp",
"shortName":"2047pp",
"icon": "app.png",
"version":"0.01",
"version":"0.02",
"description": "Bangle version of a tile shifting game",
"supports" : ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,

1
apps/openwind/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

22
apps/openwind/README.md Normal file
View File

@ -0,0 +1,22 @@
# OpenWind
Receive and display data from a wireless [OpenWind](https://www.openwind.de/) sailing wind instrument on the Bangle.
## Usage
Upon startup, the app will attempt to automatically connect to the wind instrument. This typically only takes a few seconds.
## Features
The app displays the apparent wind direction (via a green dot) and speed (green numbers, in knots) relative to the mounting direction of the wind vane.
If "True wind" is enabled in settings and a GPS fix is available, the true wind speed and direction (relative to the mounting direction of the vane) is
additionally displayed in red. In this mode, the speed over ground in knots is also shown at the bottom left of the screen.
## Controls
There are no controls in the main app, but there are two settings in the settings app that can be changed:
* True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app
* Mounting angle: mounting relative to the boat of the wind instrument (in degrees)
![](openwind_screenshot.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/AH4A/AH4AzhMJF94wtF+QwsF/4APnAACF54wZFoYxNF7guHGBQv0GCwuJGBIvFACov/AD4vvd6Yv/GCoumGIwtpAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHoA=="))

113
apps/openwind/app.js Normal file
View File

@ -0,0 +1,113 @@
OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb';
require("Font7x11Numeric7Seg").add(Graphics);
gatt = {};
cx = g.getWidth()/2;
cy = 24+(g.getHeight()-24)/2;
w = (g.getWidth()-24)/2;
gps_course = { spd: 0 };
var settings = require("Storage").readJSON('openwindsettings.json', 1) || {};
i = 0;
hullpoly = [];
for (y=-1; y<=1; y+=0.1) {
hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3;
hullpoly[i++] = cy - y*w*0.7;
}
for (y=1; y>=-1; y-=0.1) {
hullpoly[i++] = cx + (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3;
hullpoly[i++] = cy - y*w*0.7;
}
function wind_updated(ev) {
if (ev.target.uuid == "0xcc91") {
awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1;
aws = ev.target.value.getInt16(3, true)*0.01;
// console.log(awa, aws);
if (gps_course.spd > 0) {
wv = { // wind vector (in fixed reference frame)
lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws,
lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws
};
twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat };
tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2));
twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course;
if (twa<0) twa += 360;
if (twa>360) twa -=360;
}
else {
tws = -1;
twa = 0;
}
draw_compass(awa,aws,twa,tws);
}
}
function draw_compass(awa, aws, twa, tws) {
g.clearRect(0, 24, g.getWidth()-1, g.getHeight()-1);
fh = w*0.15;
g.setColor(0, 0, 1).fillPoly(hullpoly);
g.setFontVector(fh).setColor(g.theme.fg);
g.setFontAlign(0, 0, 0).drawString("0", cx, 24+fh/2);
g.setFontAlign(0, 0, 1).drawString("90", g.getWidth()-12-fh, cy);
g.setFontAlign(0, 0, 2).drawString("180", cx, g.getHeight()-fh/2);
g.setFontAlign(0, 0, 3).drawString("270", 12+fh/2, cy);
for (i=0; i<4; ++i) {
a = i*Math.PI/2+Math.PI/4;
g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99);
}
g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1);
if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1);
g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06);
g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w);
if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w);
if (settings.truewind && typeof gps_course.spd!=='undefined') {
spd = gps_course.spd/1.852;
g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1);
}
}
function parseDevice(d) {
device = d;
console.log("Found device");
device.gatt.connect().then(function(ga) {
console.log("Connected");
gatt = ga;
return ga.getPrimaryService("cc90");
}).then(function(s) {
return s.getCharacteristic("cc91");
}).then(function(c) {
c.on('characteristicvaluechanged', (event)=>wind_updated(event));
return c.startNotifications();
}).then(function() {
console.log("Done!");
}).catch(function(e) {
console.log("ERROR"+e);
});}
function connection_setup() {
NRF.setScan();
NRF.setScan(parseDevice, { filters: [{services:["cc90"]}], timeout: 2000});
console.log("Scanning for OW sensor");
}
if (settings.truewind) {
Bangle.on('GPS',function(fix) {
if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph
gps_course =
{ lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852,
lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852,
spd: fix.speed,
course: fix.course
};
}
else gps_course.spd = -1;
});
Bangle.setGPSPower(1, "app");
}
Bangle.loadWidgets();
Bangle.drawWidgets();
draw_compass(0, 0, 0, 0);
connection_setup();

BIN
apps/openwind/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

View File

@ -0,0 +1,15 @@
{ "id": "openwind",
"name": "OpenWind",
"shortName":"OpenWind",
"version":"0.01",
"description": "OpenWind",
"icon": "openwind.png",
"readme": "README.md",
"tags": "ble,outdoors,gps,sailing",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"openwind.app.js","url":"app.js"},
{"name":"openwind.img","url":"app-icon.js","evaluate":true},
{"name":"openwind.settings.js", "url":"settings.js"}
]
}

BIN
apps/openwind/openwind.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

44
apps/openwind/settings.js Normal file
View File

@ -0,0 +1,44 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
(function(back) {
const SETTINGS_FILE = 'openwindsettings.json'
// initialize with default settings...
let settings = {
'truewind': false,
'mount_angle': 0
}
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage')
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
for (const key in saved) {
settings[key] = saved[key];
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function (value) {
settings[key] = value;
storage.write(SETTINGS_FILE, settings);
}
}
const menu = {
'': { 'title': 'OpenWind' },
'< Back': back,
'True wind': {
value: settings.truewind,
format: boolFormat,
onchange: save('truewind'),
},
'Mounting angle': {
value: settings.mount_angle,
min: 0,
max: 355,
step: 5,
onchange: save('mount_angle'),
}
}
E.showMenu(menu);
})