mirror of https://github.com/espruino/BangleApps
New app "OpenWind"
parent
dc273a7a12
commit
28cd5e07a9
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -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)
|
||||||
|
|
||||||
|

|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/AH4A/AH4AzhMJF94wtF+QwsF/4APnAACF54wZFoYxNF7guHGBQv0GCwuJGBIvFACov/AD4vvd6Yv/GCoumGIwtpAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHoA=="))
|
|
@ -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();
|
Binary file not shown.
After Width: | Height: | Size: 497 B |
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 497 B |
Binary file not shown.
After Width: | Height: | Size: 152 KiB |
|
@ -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);
|
||||||
|
})
|
Loading…
Reference in New Issue