New app "OpenWind"

pull/1674/head
marko 2022-04-08 13:28:03 -04:00
parent dc273a7a12
commit 28cd5e07a9
9 changed files with 195 additions and 0 deletions

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(1, 1, 1);
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(1, 1, 1).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,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

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

@ -0,0 +1,43 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
(function(back) {
const SETTINGS_FILE = 'openwindsettings.json'
// initialize with default settings...
let s = {
'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) {
s[key] = saved[key];
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function (value) {
s[key] = value;
storage.write(SETTINGS_FILE, s);
}
}
const menu = {
'': { 'title': 'OpenWind' },
'< Back': back,
'True wind': {
value: s.truewind,
format: boolFormat,
onchange: save('truewind'),
},
'Mounting angle': {
value: s.mount_angle
min: 0,
max: 355,
step: 5,
onchange: save('mount_angle'),
}
}
E.showMenu(menu);
})