Skip to content

Commit 8c559e3

Browse files
author
thyttan
committed
Merge remote-tracking branch 'upstream/master' into app-loader
2 parents 36176d2 + b2fdb48 commit 8c559e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+917
-129
lines changed

apps/coin_info/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/dummy.js
2+
/pebbleppApp.js

apps/coin_info/ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
0.03: Initial creation
2+
0.04: Using GB http for Binance API requests
3+
0.05: Lot of cleanup
4+
0.06: Creating app v1
5+
0.07: Finishing touches for v1
6+
0.09: Clean up
7+
0.10: Finalize documentation for v1

apps/coin_info/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Crypto-Coin Info
2+
3+
Crypto-Coins Infos with the help of the Binance and CoinStats API
4+
5+
## Description
6+
7+
- Is a clock_info module and an app
8+
- I use Pebble++ watch to show a bigger size of clock_info
9+
- I use a wider, more readable font for Pebble++
10+
- Upload data via App-Loader interface first!!!
11+
12+
![Screenshot Click_Info 01](screenshots/20250316_01.jpg)
13+
![Screenshot Click_Info 02](screenshots/20250316_02.jpg)
14+
![Screenshot App BTC Graph](screenshots/20250322_01.jpg)
15+
![Screenshot APP BTC Details](screenshots/20250322_02.jpg)
16+
![Screenshot APP STORJ Low/High](screenshots/20250323_01.jpg)
17+
18+
## Creator
19+
20+
Martin Zwigl
21+
22+
## Parts Infos
23+
24+
### App-Loader web-interface
25+
26+
- Binance
27+
- Find docs here [Binance API](https://www.binance.com/en/binance-api)
28+
- For Binance use symbols like BTC,ETH,STORJ
29+
- For the calc counterpart use USDT (I don't know why USD is measured on the stablecoin) or EUR or other fiat currency
30+
- Coinstats
31+
- Find docs here [Coinstats API](https://openapi.coinstats.app/)
32+
- Get an API key at the website. Free is worth 1Mio token, which in turn is worth around 250k - 300k requests per month
33+
- Supply crypto token in the form of its IDs like bitcoin,ethereum,storj
34+
- It is not necessary to re-upload the app when uploading data. Data is read with app start
35+
36+
### Clock-Info
37+
38+
- Updates prices with the free Binance API
39+
- clkInfo updates after around 15 sec and then every x minutes (via settings) thereafter.
40+
- The token you want to have tracked and compared to what currency have to be uploaded via app loader web-interface
41+
- After that you can decide which token to display in settings
42+
43+
### App
44+
45+
- Using CoinStats for chart-data
46+
- token-names on CoinStats are different to Binance; they also have to be uploaded via Interface
47+
- You also need a CoinStats API access key which is good for a fair amount of calls
48+
- I tried with gridy for the axis, but for this data - it is just not readable...
49+
- Let me know when you have good suggestions for improvement.
50+
- ".." button shows current details for current token
51+
- "LH" button shows low and high on graph as well as the first and last point in series
52+
- Swipe L-R changes token you supplied via interface
53+
- Not much guard-rails in the app -> you should have at least one token (each) present
54+
- Also the API token and fiat currency you want to match against (eg. USD, EUR)
55+
- New data is requested every minute, except on button touch
56+
57+
### Settings
58+
59+
- Choose which of the uploaded tokens to display in clock_info
60+
- Choose update-time for clock_info HTTP requests to Binance
61+
62+
## Possible Improvements / TODOs
63+
64+
- Better choosing of fonts for more space
65+
- set UI properly to have back button next to widgets
66+
- clean-up code structure

apps/coin_info/app-icon.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/coin_info/app.js

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// const logFile = require("Storage").open("coin_info_log.txt", "a");
2+
const db = require("Storage").readJSON("coin_info.cmc_key.json", 1) || {};
3+
const csTokens = db.csTokens.split(',');
4+
//
5+
const ciLib = require("coin_info");
6+
//
7+
var ticker = 0;
8+
var currLoadMsg = "...";
9+
var timePeriod = "24h";
10+
var tknChrtData = [5,6,5,6,5,6,5,6,5,6,5,6,5,6,];
11+
var optSpacing = {};
12+
var isPaused = false;
13+
14+
15+
//
16+
Bangle.loadWidgets(); // loading widgets after drawing the layout in `drawMain()` to display the app UI ASAP.
17+
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
18+
// Bangle.setUI({
19+
// mode: 'custom',
20+
// back: Bangle.showClock
21+
// // btn: function() { // Handle button press
22+
// // console.log("Button pressed");
23+
// // }
24+
// });
25+
//
26+
function swipeHandler(lr, ud) {
27+
if (lr == 1) {
28+
ticker = ticker - 1;
29+
if (ticker < 0) ticker = 0;
30+
}
31+
if (lr == -1) {
32+
ticker = ticker + 1;
33+
if (ticker > csTokens.length - 1) ticker = csTokens.length - 1;
34+
}
35+
}
36+
Bangle.on("swipe", swipeHandler);
37+
38+
39+
//
40+
function renderGraph(l) {
41+
const bounds = ciLib.findMinMax(tknChrtData);
42+
// logFile.write("?. graphy: " + JSON.stringify(bounds) + "\n");
43+
require("graph").drawLine(g, tknChrtData, {
44+
axes: true,
45+
x: l.x, y: l.y, width: l.w, height: l.h,
46+
miny: bounds.min,
47+
maxy: bounds.max,
48+
// gridy: 5
49+
});
50+
}
51+
52+
var Layout = require("Layout");
53+
var layout = new Layout({
54+
type:"v", c: [
55+
{type:"h", valign:-1,
56+
c: [
57+
{type:"txt", id:"tknName", font:"6x8:2", label:"", halign:-1, fillx:1},
58+
{type:"btn", label:"..", halign:1, cb: d=>showDetails()},
59+
{type:"btn", label:"LH", halign:1, cb: d=>showLowHigh()}
60+
]
61+
},
62+
{type:"txt", id:"loadMsg", font:"6x8", label:"", fillx:1 },
63+
{type:"custom", render:renderGraph, id:"tknGraph", bgCol:g.theme.bg, fillx:1, filly:1 },
64+
{type:"h", valign:1,
65+
c: [
66+
{type:"btn", label:"24h", cb: d=>getChart("24h")},
67+
{type:"btn", label:"1w", cb: d=>getChart("1w")},
68+
{type:"btn", label:"1m", cb: d=>getChart("1m")},
69+
{type:"btn", label:"3m", cb: d=>getChart("3m")}
70+
]
71+
}
72+
]
73+
},
74+
{ lazy:true });
75+
layout.update();
76+
77+
78+
//
79+
var updateTimeout;
80+
function getChart(period) {
81+
if (isPaused) {
82+
if (updateTimeout) clearTimeout(updateTimeout);
83+
return;
84+
}
85+
86+
//
87+
timePeriod = period;
88+
currLoadMsg = `Load... ${period}`;
89+
//
90+
// const date = new Date();
91+
// logFile.write("Called:" + date.toISOString() + " -- " + timePeriod + " -- " + csTokens[ticker] + "\n");
92+
93+
const url = `https://openapiv1.coinstats.app/coins/${csTokens[ticker]}/charts?period=${timePeriod}`;
94+
Bangle
95+
.http(url, {
96+
method: 'GET',
97+
headers: {
98+
'X-API-KEY': db.csApiKey
99+
}
100+
})
101+
.then(data => {
102+
// logFile.write("HTTP resp:" + JSON.stringify(data));
103+
const apiData = JSON.parse(data.resp);
104+
tknChrtData = apiData.map(innerArray => innerArray[1]);
105+
// logFile.write("Chart data:" + JSON.stringify(tknChrtData));
106+
107+
// just not readable
108+
optSpacing = ciLib.calculateOptimalYAxisSpacing(tknChrtData);
109+
//
110+
g.clearRect(layout.tknGraph.x, layout.tknGraph.y, layout.tknGraph.w, layout.tknGraph.h);
111+
layout.forgetLazyState(); // Force a full re-render
112+
layout.render(layout.tknGraph); // Render just the graph area
113+
114+
//
115+
currLoadMsg = "";
116+
layout.render(layout.loadMsg);
117+
})
118+
.catch(err => {
119+
// logFile.write("API Error: " + JSON.stringify(err));
120+
tknChrtData = [1,2,3,4,5,6,7,8,9,8,7,6,5,4,];
121+
});
122+
123+
if (updateTimeout) clearTimeout(updateTimeout);
124+
updateTimeout = setTimeout(function() {
125+
updateTimeout = undefined;
126+
getChart(period);
127+
}, 60000 - (Date.now() % 60000));
128+
}
129+
130+
//
131+
function showLowHigh() {
132+
const title = `L/H ${csTokens[ticker]}`;
133+
//
134+
// logFile.write("OptSpacing:" + JSON.stringify(optSpacing) + "\n");
135+
const first = ciLib.formatPriceString(optSpacing.first);
136+
const last = ciLib.formatPriceString(optSpacing.last);
137+
const low = ciLib.formatPriceString(optSpacing.rawMin);
138+
const high = ciLib.formatPriceString(optSpacing.rawMax);
139+
const msg = `
140+
First: ${first}
141+
Last: ${last}
142+
Low: ${low}
143+
High: ${high}
144+
`;
145+
isPaused = true;
146+
E.showAlert(msg, title).then(function() {
147+
isPaused = false;
148+
g.clear();
149+
layout.forgetLazyState();
150+
layout.render();
151+
layout.setUI();
152+
});
153+
}
154+
function showDetails() {
155+
const token = csTokens[ticker];
156+
const url = `https://openapiv1.coinstats.app/coins/${token}`;
157+
Bangle.http(url, {
158+
method: 'GET',
159+
headers: {
160+
'X-API-KEY': db.csApiKey
161+
}
162+
})
163+
.then(data => {
164+
const tokenInfo = JSON.parse(data.resp);
165+
const priceFmt = ciLib.formatPriceString(tokenInfo.price);
166+
const mCapFmt = ciLib.formatPriceString(tokenInfo.marketCap);
167+
const title = `Details ${tokenInfo.symbol}`;
168+
const msg = `
169+
Price: ${priceFmt}
170+
M-Cap: ${mCapFmt}
171+
1h:${tokenInfo.priceChange1h}
172+
1d:${tokenInfo.priceChange1d} 1w:${tokenInfo.priceChange1w}
173+
`;
174+
isPaused = true;
175+
E.showAlert(msg, title).then(function() {
176+
isPaused = false;
177+
g.clear();
178+
layout.forgetLazyState();
179+
layout.render();
180+
layout.setUI();
181+
});
182+
})
183+
.catch(err => {
184+
const msg = `Failed to fetch details for ${token.toUpperCase()}`;
185+
E.showAlert(msg, "Error").then(function() {
186+
// print("Ok pressed");
187+
g.clear();
188+
layout.forgetLazyState();
189+
layout.render();
190+
layout.setUI();
191+
});
192+
});
193+
}
194+
195+
196+
// timeout used to update every minute
197+
var drawTimeout;
198+
// update the screen
199+
function draw() {
200+
//
201+
layout.tknName.label = (csTokens[ticker]).toUpperCase();
202+
layout.loadMsg.label = currLoadMsg;
203+
//
204+
layout.render(layout.graph);
205+
//
206+
layout.render();
207+
208+
// schedule a draw for the next minute
209+
if (drawTimeout) clearTimeout(drawTimeout);
210+
drawTimeout = setTimeout(function() {
211+
drawTimeout = undefined;
212+
draw();
213+
}, 1000 - (Date.now() % 1000));
214+
}
215+
216+
// update time and draw
217+
g.clear();
218+
draw();
219+
getChart("24h");

apps/coin_info/app.png

1.63 KB
Loading

apps/coin_info/app_mono.png

1005 Bytes
Loading

apps/coin_info/clkinfo.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
(function() {
2+
const LOAD_ICON_24 = atob("GBiBAAAAAAAeAAGfwAGB4AAAcBgAOBgYHAAYDAAYDGAYBmAYBgAYBgAYBmDbBmB+BgA8DAAYDBgAHBgAOAAAcAGB4AGfwAAeAAAAAA==");
3+
const DECR_ICON_24 = atob("GBiBAAAAAAAAAAAAAAAAAAAAABgAADwAAH4cAD8+AB//AA//gAf/wAPz7AHh/ADA/AAAfAAA/gAA/gAAHgAAAAAAAAAAAAAAAAAAAA==");
4+
const INCR_ICON_24 = atob("GBiBAAAAAAAAAAAAAAAAAAAAAAAAHgAA/gAA/gAAfADA/AHh/APz7Af/wA//gB//AD8+AH4cADwAABgAAAAAAAAAAAAAAAAAAAAAAA==");
5+
6+
const settings = require("Storage").readJSON("coin_info.settings.json", 1) || {};
7+
const db = require("Storage").readJSON("coin_info.cmc_key.json", 1) || {};
8+
// const logFile = require("Storage").open("coin_info_log.txt", "a");
9+
const ciLib = require("coin_info");
10+
11+
if (!(settings.tokenSelected instanceof Array)) settings.tokenSelected = [];
12+
13+
let cache = {};
14+
return {
15+
name: "CoinInfo",
16+
items: settings.tokenSelected.map(token => {
17+
return {
18+
name: token,
19+
get: function() {
20+
// Return cached data if available
21+
if (cache[token]) {
22+
return cache[token];
23+
}
24+
25+
// Return placeholder while waiting for data
26+
return {
27+
text: "Load",
28+
img: LOAD_ICON_24
29+
};
30+
},
31+
show: function() {
32+
var self = this;
33+
34+
// Function to fetch data from API
35+
const fetchData = (callback) => {
36+
const url = `https://api.binance.com/api/v3/ticker/24hr?symbol=${token}${db.calcPair}`;
37+
38+
Bangle.http(url, { method: 'GET' })
39+
.then(cmcResult => {
40+
// logFile.write("HTTP resp:" + JSON.stringify(cmcResult));
41+
const apiData = JSON.parse(cmcResult.resp);
42+
// logFile.write("data:" + JSON.stringify(apiData));
43+
let priceString = ciLib.formatPriceString(apiData.lastPrice);
44+
45+
let changeIcon = INCR_ICON_24;
46+
if (apiData.priceChange.startsWith("-"))
47+
changeIcon = DECR_ICON_24;
48+
// Update cache with fetched data
49+
cache[token] = {
50+
text: `${token}\n${priceString}`,
51+
img: changeIcon
52+
};
53+
54+
callback();
55+
})
56+
.catch(err => {
57+
// logFile.write("API Error: " + JSON.stringify(err));
58+
cache[token] = {
59+
text: "Error",
60+
img: LOAD_ICON_24
61+
};
62+
callback();
63+
});
64+
};
65+
66+
// Set timeout to align to the next hour and then continue updating every hour
67+
const updateTime = settings.getRateMin * 60 * 1000;
68+
self.interval = setTimeout(function timerTimeout() {
69+
fetchData(() => {
70+
self.emit("redraw");
71+
});
72+
// Continue updating every hour
73+
self.interval = setInterval(function intervalCallback() {
74+
fetchData(() => {
75+
self.emit("redraw");
76+
});
77+
}, updateTime);
78+
}, 30000 - (Date.now() % 30000 ));
79+
},
80+
hide: function() {
81+
if (this.interval) {
82+
clearInterval(this.interval);
83+
this.interval = null;
84+
}
85+
}
86+
};
87+
})
88+
};
89+
})
306 Bytes
Loading
303 Bytes
Loading

0 commit comments

Comments
 (0)