Merge pull request #3781 from zwiglm/master
Crypto-Coins Infos with the help of the Binance and CoinStats APImaster
|
|
@ -0,0 +1,2 @@
|
||||||
|
/dummy.js
|
||||||
|
/pebbleppApp.js
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
0.03: Initial creation
|
||||||
|
0.04: Using GB http for Binance API requests
|
||||||
|
0.05: Lot of cleanup
|
||||||
|
0.06: Creating app v1
|
||||||
|
0.07: Finishing touches for v1
|
||||||
|
0.09: Clean up
|
||||||
|
0.10: Finalize documentation for v1
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Crypto-Coin Info
|
||||||
|
|
||||||
|
Crypto-Coins Infos with the help of the Binance and CoinStats API
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
- Is a clock_info module and an app
|
||||||
|
- I use Pebble++ watch to show a bigger size of clock_info
|
||||||
|
- I use a wider, more readable font for Pebble++
|
||||||
|
- Upload data via App-Loader interface first!!!
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
Martin Zwigl
|
||||||
|
|
||||||
|
## Parts Infos
|
||||||
|
|
||||||
|
### App-Loader web-interface
|
||||||
|
|
||||||
|
- Binance
|
||||||
|
- Find docs here [Binance API](https://www.binance.com/en/binance-api)
|
||||||
|
- For Binance use symbols like BTC,ETH,STORJ
|
||||||
|
- For the calc counterpart use USDT (I don't know why USD is measured on the stablecoin) or EUR or other fiat currency
|
||||||
|
- Coinstats
|
||||||
|
- Find docs here [Coinstats API](https://openapi.coinstats.app/)
|
||||||
|
- Get an API key at the website. Free is worth 1Mio token, which in turn is worth around 250k - 300k requests per month
|
||||||
|
- Supply crypto token in the form of its IDs like bitcoin,ethereum,storj
|
||||||
|
- It is not necessary to re-upload the app when uploading data. Data is read with app start
|
||||||
|
|
||||||
|
### Clock-Info
|
||||||
|
|
||||||
|
- Updates prices with the free Binance API
|
||||||
|
- clkInfo updates after around 15 sec and then every x minutes (via settings) thereafter.
|
||||||
|
- The token you want to have tracked and compared to what currency have to be uploaded via app loader web-interface
|
||||||
|
- After that you can decide which token to display in settings
|
||||||
|
|
||||||
|
### App
|
||||||
|
|
||||||
|
- Using CoinStats for chart-data
|
||||||
|
- token-names on CoinStats are different to Binance; they also have to be uploaded via Interface
|
||||||
|
- You also need a CoinStats API access key which is good for a fair amount of calls
|
||||||
|
- I tried with gridy for the axis, but for this data - it is just not readable...
|
||||||
|
- Let me know when you have good suggestions for improvement.
|
||||||
|
- ".." button shows current details for current token
|
||||||
|
- "LH" button shows low and high on graph as well as the first and last point in series
|
||||||
|
- Swipe L-R changes token you supplied via interface
|
||||||
|
- Not much guard-rails in the app -> you should have at least one token (each) present
|
||||||
|
- Also the API token and fiat currency you want to match against (eg. USD, EUR)
|
||||||
|
- New data is requested every minute, except on button touch
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
- Choose which of the uploaded tokens to display in clock_info
|
||||||
|
- Choose update-time for clock_info HTTP requests to Binance
|
||||||
|
|
||||||
|
## Possible Improvements / TODOs
|
||||||
|
|
||||||
|
- Better choosing of fonts for more space
|
||||||
|
- set UI properly to have back button next to widgets
|
||||||
|
- clean-up code structure
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AT5gAGFdYzmFx4xfEAfN53U6oAC53O5owg6wjB5wrDAA3UGQxaW5vWAAgwLGIIwXRAYvFGBxiEXCnOF4wwBKoKWHGAPUGCguLAAINCMJIwEFzgvLGC3NFw7gFAAfNehovN65aKABDEGGAouNAAIvSSg6RRF4QwGDAQIF6qSLMBwuDGAwvIGAj0IF6YwEF5IKDF4/VSAQtIO4QwJL6vVSBaoDGBC/UF5gJCGBQNEAA/VGoIAEBIIvEGQYaHGA4vML4IwEFgYAFF5PM54vSGAKXCL4gvOIIXVF5DvG6xWD5wwMYBAUCR5IvHGAZgCGBLwJFo4vEegIAHHgwwGFwwwD5wvMGBHNL4gwFFpBgEF5owI53VA4ovQ6wvNSRIAGFxYvD5ovKAAyKFAA4vLMBYvJagIuXMBfOL6fN5ovNGAfOeRD0PFwIPCGCPPF54wHOQYuOF4YwKEIJtFFxAvQGAiSLAAqMGFyIwFcQIwP6ouXGAoxOFoouVGI6VB6ozDRAPVXAgtZGBAANFzQxSFrozNFcYA/AH4AvA"))
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
// const logFile = require("Storage").open("coin_info_log.txt", "a");
|
||||||
|
const db = require("Storage").readJSON("coin_info.cmc_key.json", 1) || {};
|
||||||
|
const csTokens = db.csTokens.split(',');
|
||||||
|
//
|
||||||
|
const ciLib = require("coin_info");
|
||||||
|
//
|
||||||
|
var ticker = 0;
|
||||||
|
var currLoadMsg = "...";
|
||||||
|
var timePeriod = "24h";
|
||||||
|
var tknChrtData = [5,6,5,6,5,6,5,6,5,6,5,6,5,6,];
|
||||||
|
var optSpacing = {};
|
||||||
|
var isPaused = false;
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
Bangle.loadWidgets(); // loading widgets after drawing the layout in `drawMain()` to display the app UI ASAP.
|
||||||
|
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||||
|
// Bangle.setUI({
|
||||||
|
// mode: 'custom',
|
||||||
|
// back: Bangle.showClock
|
||||||
|
// // btn: function() { // Handle button press
|
||||||
|
// // console.log("Button pressed");
|
||||||
|
// // }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
function swipeHandler(lr, ud) {
|
||||||
|
if (lr == 1) {
|
||||||
|
ticker = ticker - 1;
|
||||||
|
if (ticker < 0) ticker = 0;
|
||||||
|
}
|
||||||
|
if (lr == -1) {
|
||||||
|
ticker = ticker + 1;
|
||||||
|
if (ticker > csTokens.length - 1) ticker = csTokens.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bangle.on("swipe", swipeHandler);
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
function renderGraph(l) {
|
||||||
|
const bounds = ciLib.findMinMax(tknChrtData);
|
||||||
|
// logFile.write("?. graphy: " + JSON.stringify(bounds) + "\n");
|
||||||
|
require("graph").drawLine(g, tknChrtData, {
|
||||||
|
axes: true,
|
||||||
|
x: l.x, y: l.y, width: l.w, height: l.h,
|
||||||
|
miny: bounds.min,
|
||||||
|
maxy: bounds.max,
|
||||||
|
// gridy: 5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var Layout = require("Layout");
|
||||||
|
var layout = new Layout({
|
||||||
|
type:"v", c: [
|
||||||
|
{type:"h", valign:-1,
|
||||||
|
c: [
|
||||||
|
{type:"txt", id:"tknName", font:"6x8:2", label:"", halign:-1, fillx:1},
|
||||||
|
{type:"btn", label:"..", halign:1, cb: d=>showDetails()},
|
||||||
|
{type:"btn", label:"LH", halign:1, cb: d=>showLowHigh()}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{type:"txt", id:"loadMsg", font:"6x8", label:"", fillx:1 },
|
||||||
|
{type:"custom", render:renderGraph, id:"tknGraph", bgCol:g.theme.bg, fillx:1, filly:1 },
|
||||||
|
{type:"h", valign:1,
|
||||||
|
c: [
|
||||||
|
{type:"btn", label:"24h", cb: d=>getChart("24h")},
|
||||||
|
{type:"btn", label:"1w", cb: d=>getChart("1w")},
|
||||||
|
{type:"btn", label:"1m", cb: d=>getChart("1m")},
|
||||||
|
{type:"btn", label:"3m", cb: d=>getChart("3m")}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ lazy:true });
|
||||||
|
layout.update();
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
var updateTimeout;
|
||||||
|
function getChart(period) {
|
||||||
|
if (isPaused) {
|
||||||
|
if (updateTimeout) clearTimeout(updateTimeout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
timePeriod = period;
|
||||||
|
currLoadMsg = `Load... ${period}`;
|
||||||
|
//
|
||||||
|
// const date = new Date();
|
||||||
|
// logFile.write("Called:" + date.toISOString() + " -- " + timePeriod + " -- " + csTokens[ticker] + "\n");
|
||||||
|
|
||||||
|
const url = `https://openapiv1.coinstats.app/coins/${csTokens[ticker]}/charts?period=${timePeriod}`;
|
||||||
|
Bangle
|
||||||
|
.http(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-API-KEY': db.csApiKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
// logFile.write("HTTP resp:" + JSON.stringify(data));
|
||||||
|
const apiData = JSON.parse(data.resp);
|
||||||
|
tknChrtData = apiData.map(innerArray => innerArray[1]);
|
||||||
|
// logFile.write("Chart data:" + JSON.stringify(tknChrtData));
|
||||||
|
|
||||||
|
// just not readable
|
||||||
|
optSpacing = ciLib.calculateOptimalYAxisSpacing(tknChrtData);
|
||||||
|
//
|
||||||
|
g.clearRect(layout.tknGraph.x, layout.tknGraph.y, layout.tknGraph.w, layout.tknGraph.h);
|
||||||
|
layout.forgetLazyState(); // Force a full re-render
|
||||||
|
layout.render(layout.tknGraph); // Render just the graph area
|
||||||
|
|
||||||
|
//
|
||||||
|
currLoadMsg = "";
|
||||||
|
layout.render(layout.loadMsg);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// logFile.write("API Error: " + JSON.stringify(err));
|
||||||
|
tknChrtData = [1,2,3,4,5,6,7,8,9,8,7,6,5,4,];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updateTimeout) clearTimeout(updateTimeout);
|
||||||
|
updateTimeout = setTimeout(function() {
|
||||||
|
updateTimeout = undefined;
|
||||||
|
getChart(period);
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
function showLowHigh() {
|
||||||
|
const title = `L/H ${csTokens[ticker]}`;
|
||||||
|
//
|
||||||
|
// logFile.write("OptSpacing:" + JSON.stringify(optSpacing) + "\n");
|
||||||
|
const first = ciLib.formatPriceString(optSpacing.first);
|
||||||
|
const last = ciLib.formatPriceString(optSpacing.last);
|
||||||
|
const low = ciLib.formatPriceString(optSpacing.rawMin);
|
||||||
|
const high = ciLib.formatPriceString(optSpacing.rawMax);
|
||||||
|
const msg = `
|
||||||
|
First: ${first}
|
||||||
|
Last: ${last}
|
||||||
|
Low: ${low}
|
||||||
|
High: ${high}
|
||||||
|
`;
|
||||||
|
isPaused = true;
|
||||||
|
E.showAlert(msg, title).then(function() {
|
||||||
|
isPaused = false;
|
||||||
|
g.clear();
|
||||||
|
layout.forgetLazyState();
|
||||||
|
layout.render();
|
||||||
|
layout.setUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function showDetails() {
|
||||||
|
const token = csTokens[ticker];
|
||||||
|
const url = `https://openapiv1.coinstats.app/coins/${token}`;
|
||||||
|
Bangle.http(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'X-API-KEY': db.csApiKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
const tokenInfo = JSON.parse(data.resp);
|
||||||
|
const priceFmt = ciLib.formatPriceString(tokenInfo.price);
|
||||||
|
const mCapFmt = ciLib.formatPriceString(tokenInfo.marketCap);
|
||||||
|
const title = `Details ${tokenInfo.symbol}`;
|
||||||
|
const msg = `
|
||||||
|
Price: ${priceFmt}
|
||||||
|
M-Cap: ${mCapFmt}
|
||||||
|
1h:${tokenInfo.priceChange1h}
|
||||||
|
1d:${tokenInfo.priceChange1d} 1w:${tokenInfo.priceChange1w}
|
||||||
|
`;
|
||||||
|
isPaused = true;
|
||||||
|
E.showAlert(msg, title).then(function() {
|
||||||
|
isPaused = false;
|
||||||
|
g.clear();
|
||||||
|
layout.forgetLazyState();
|
||||||
|
layout.render();
|
||||||
|
layout.setUI();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
const msg = `Failed to fetch details for ${token.toUpperCase()}`;
|
||||||
|
E.showAlert(msg, "Error").then(function() {
|
||||||
|
// print("Ok pressed");
|
||||||
|
g.clear();
|
||||||
|
layout.forgetLazyState();
|
||||||
|
layout.render();
|
||||||
|
layout.setUI();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// timeout used to update every minute
|
||||||
|
var drawTimeout;
|
||||||
|
// update the screen
|
||||||
|
function draw() {
|
||||||
|
//
|
||||||
|
layout.tknName.label = (csTokens[ticker]).toUpperCase();
|
||||||
|
layout.loadMsg.label = currLoadMsg;
|
||||||
|
//
|
||||||
|
layout.render(layout.graph);
|
||||||
|
//
|
||||||
|
layout.render();
|
||||||
|
|
||||||
|
// schedule a draw for the next minute
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 1000 - (Date.now() % 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update time and draw
|
||||||
|
g.clear();
|
||||||
|
draw();
|
||||||
|
getChart("24h");
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1005 B |
|
|
@ -0,0 +1,89 @@
|
||||||
|
(function() {
|
||||||
|
const LOAD_ICON_24 = atob("GBiBAAAAAAAeAAGfwAGB4AAAcBgAOBgYHAAYDAAYDGAYBmAYBgAYBgAYBmDbBmB+BgA8DAAYDBgAHBgAOAAAcAGB4AGfwAAeAAAAAA==");
|
||||||
|
const DECR_ICON_24 = atob("GBiBAAAAAAAAAAAAAAAAAAAAABgAADwAAH4cAD8+AB//AA//gAf/wAPz7AHh/ADA/AAAfAAA/gAA/gAAHgAAAAAAAAAAAAAAAAAAAA==");
|
||||||
|
const INCR_ICON_24 = atob("GBiBAAAAAAAAAAAAAAAAAAAAAAAAHgAA/gAA/gAAfADA/AHh/APz7Af/wA//gB//AD8+AH4cADwAABgAAAAAAAAAAAAAAAAAAAAAAA==");
|
||||||
|
|
||||||
|
const settings = require("Storage").readJSON("coin_info.settings.json", 1) || {};
|
||||||
|
const db = require("Storage").readJSON("coin_info.cmc_key.json", 1) || {};
|
||||||
|
// const logFile = require("Storage").open("coin_info_log.txt", "a");
|
||||||
|
const ciLib = require("coin_info");
|
||||||
|
|
||||||
|
if (!(settings.tokenSelected instanceof Array)) settings.tokenSelected = [];
|
||||||
|
|
||||||
|
let cache = {};
|
||||||
|
return {
|
||||||
|
name: "CoinInfo",
|
||||||
|
items: settings.tokenSelected.map(token => {
|
||||||
|
return {
|
||||||
|
name: token,
|
||||||
|
get: function() {
|
||||||
|
// Return cached data if available
|
||||||
|
if (cache[token]) {
|
||||||
|
return cache[token];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return placeholder while waiting for data
|
||||||
|
return {
|
||||||
|
text: "Load",
|
||||||
|
img: LOAD_ICON_24
|
||||||
|
};
|
||||||
|
},
|
||||||
|
show: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Function to fetch data from API
|
||||||
|
const fetchData = (callback) => {
|
||||||
|
const url = `https://api.binance.com/api/v3/ticker/24hr?symbol=${token}${db.calcPair}`;
|
||||||
|
|
||||||
|
Bangle.http(url, { method: 'GET' })
|
||||||
|
.then(cmcResult => {
|
||||||
|
// logFile.write("HTTP resp:" + JSON.stringify(cmcResult));
|
||||||
|
const apiData = JSON.parse(cmcResult.resp);
|
||||||
|
// logFile.write("data:" + JSON.stringify(apiData));
|
||||||
|
let priceString = ciLib.formatPriceString(apiData.lastPrice);
|
||||||
|
|
||||||
|
let changeIcon = INCR_ICON_24;
|
||||||
|
if (apiData.priceChange.startsWith("-"))
|
||||||
|
changeIcon = DECR_ICON_24;
|
||||||
|
// Update cache with fetched data
|
||||||
|
cache[token] = {
|
||||||
|
text: `${token}\n${priceString}`,
|
||||||
|
img: changeIcon
|
||||||
|
};
|
||||||
|
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// logFile.write("API Error: " + JSON.stringify(err));
|
||||||
|
cache[token] = {
|
||||||
|
text: "Error",
|
||||||
|
img: LOAD_ICON_24
|
||||||
|
};
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set timeout to align to the next hour and then continue updating every hour
|
||||||
|
const updateTime = settings.getRateMin * 60 * 1000;
|
||||||
|
self.interval = setTimeout(function timerTimeout() {
|
||||||
|
fetchData(() => {
|
||||||
|
self.emit("redraw");
|
||||||
|
});
|
||||||
|
// Continue updating every hour
|
||||||
|
self.interval = setInterval(function intervalCallback() {
|
||||||
|
fetchData(() => {
|
||||||
|
self.emit("redraw");
|
||||||
|
});
|
||||||
|
}, updateTime);
|
||||||
|
}, 30000 - (Date.now() % 30000 ));
|
||||||
|
},
|
||||||
|
hide: function() {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.interval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 303 B |
|
After Width: | Height: | Size: 508 B |
|
|
@ -0,0 +1,80 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Settings for Crypto-Coin Info</h3>
|
||||||
|
<p>Input for coins to request and calculate against pair.</p>
|
||||||
|
<p>
|
||||||
|
<label for="traceTokens">Trace from Binance eg. BTC,ETH,STORj</label><br>
|
||||||
|
<input id="traceTokens" onkeyup="checkInput()" style="width:90%; margin: 3px"></input>
|
||||||
|
|
||||||
|
<label for="calcPair">Calc price against what currency eg. USDT or EUR</label><br>
|
||||||
|
<input id="calcPair" onkeyup="checkInput()" style="width:90%; margin: 3px"></input>
|
||||||
|
|
||||||
|
<label for="csTokens">Trace from Coinstats eg. bitcoin,ethereum,storj</label><br>
|
||||||
|
<input id="csTokens" onkeyup="checkInput()" style="width:90%; margin: 3px"></input>
|
||||||
|
|
||||||
|
<label for="csApiKey">CoinStats API Key</label><br>
|
||||||
|
<input id="csApiKey" onkeyup="checkInput()" style="width:90%; margin: 3px"></input>
|
||||||
|
|
||||||
|
<button id="upload" class="btn btn-primary">Save</button>
|
||||||
|
</p>
|
||||||
|
<h4>General Info:</h4>
|
||||||
|
<p>Requesting from Binance with free API. Get info from their website.</p>
|
||||||
|
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function checkInput() {
|
||||||
|
if(document.getElementById("calcPair").value==="") {
|
||||||
|
document.getElementById('upload').disabled = true;
|
||||||
|
} else {
|
||||||
|
document.getElementById('upload').disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkInput();
|
||||||
|
|
||||||
|
var settings = {};
|
||||||
|
function onInit(){
|
||||||
|
console.log("Loading settings from BangleJs...");
|
||||||
|
try {
|
||||||
|
Util.readStorageJSON("coin_info.cmc_key.json", data=>{
|
||||||
|
if(data){
|
||||||
|
settings = data;
|
||||||
|
console.log("Got settings", settings);
|
||||||
|
document.getElementById("calcPair").value = settings.calcPair;
|
||||||
|
document.getElementById("traceTokens").value = settings.traceTokens;
|
||||||
|
document.getElementById("csTokens").value = settings.csTokens;
|
||||||
|
document.getElementById("csApiKey").value = settings.csApiKey;
|
||||||
|
console.log("Loaded settings from BangleJs.");
|
||||||
|
checkInput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(ex) {
|
||||||
|
console.log("(Warning) Could not load settings from BangleJs.");
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("upload").addEventListener("click", function() {
|
||||||
|
try {
|
||||||
|
settings.calcPair = document.getElementById("calcPair").value;
|
||||||
|
settings.traceTokens = document.getElementById("traceTokens").value;
|
||||||
|
settings.csTokens = document.getElementById("csTokens").value;
|
||||||
|
settings.csApiKey = document.getElementById("csApiKey").value;
|
||||||
|
Util.showModal("Saving...");
|
||||||
|
Util.writeStorage("coin_info.cmc_key.json", JSON.stringify(settings), ()=>{
|
||||||
|
Util.hideModal();
|
||||||
|
});
|
||||||
|
console.log("Sent settings!");
|
||||||
|
} catch(ex) {
|
||||||
|
console.log("(Warning) Could not write settings to BangleJs.");
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* Formats a number string with common prefixes like K, M, B, T.
|
||||||
|
* For negative numbers, it returns "Negative".
|
||||||
|
* For numbers between 0 and 1, it rounds up to two decimal places.
|
||||||
|
*
|
||||||
|
* @param {string} input - The input string containing numerals.
|
||||||
|
* @returns {string} The formatted string.
|
||||||
|
*/
|
||||||
|
exports.formatPriceString = function(input) {
|
||||||
|
// Ensure input is a number
|
||||||
|
let number = typeof input === 'string' ? parseFloat(input) : input;
|
||||||
|
|
||||||
|
// Check if input is not a number
|
||||||
|
if (isNaN(number)) {
|
||||||
|
return 'Invalid input';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle negative numbers
|
||||||
|
if (number < 0) {
|
||||||
|
return 'Negative';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle zero
|
||||||
|
if (number === 0) {
|
||||||
|
return 'Zero';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle numbers between 0 and 1
|
||||||
|
if (number < 1) {
|
||||||
|
return Math.ceil(number * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define suffixes
|
||||||
|
const suffixes = ['', 'K', 'M', 'B', 'T'];
|
||||||
|
|
||||||
|
// Determine the suffix index
|
||||||
|
let suffixIndex = 0;
|
||||||
|
while (number >= 1000 && suffixIndex < suffixes.length - 1) {
|
||||||
|
number /= 1000;
|
||||||
|
suffixIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the number with three decimal places after the comma
|
||||||
|
const formattedNumber = number.toFixed(3) + suffixes[suffixIndex];
|
||||||
|
|
||||||
|
return formattedNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.findMinMax = function(values) {
|
||||||
|
var min = values[0];
|
||||||
|
var max = values[0];
|
||||||
|
|
||||||
|
for (var i = 1; i < values.length; i++) {
|
||||||
|
if (values[i] < min) min = values[i];
|
||||||
|
if (values[i] > max) max = values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { min: min, max: max };
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.myLog10 = function(value) {
|
||||||
|
return Math.log(value) / Math.LN10;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.calculateOptimalYAxisSpacing = function(data) {
|
||||||
|
// Check if data is empty
|
||||||
|
if (data.length === 0) {
|
||||||
|
return { min: 0, max: 1, interval: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the minimum and maximum values in the data
|
||||||
|
const bounds = exports.findMinMax(data);
|
||||||
|
let minY = bounds.min;
|
||||||
|
let maxY = bounds.max;
|
||||||
|
|
||||||
|
// Calculate the range of the data
|
||||||
|
let range = maxY - minY;
|
||||||
|
|
||||||
|
// If all values are the same, set a small range to avoid division by zero
|
||||||
|
if (range === 0) {
|
||||||
|
range = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the number of ticks (e.g., 5 to 10 ticks)
|
||||||
|
let numTicks = 7; // You can adjust this value based on your preference
|
||||||
|
|
||||||
|
// Calculate the interval
|
||||||
|
let interval = range / (numTicks - 1);
|
||||||
|
|
||||||
|
// Round the interval to a nice number (e.g., 1, 2, 5, 10)
|
||||||
|
let roundedInterval = Math.pow(10, Math.floor(exports.myLog10(interval)));
|
||||||
|
if (interval / roundedInterval > 5) {
|
||||||
|
roundedInterval *= 5;
|
||||||
|
} else if (interval / roundedInterval > 2) {
|
||||||
|
roundedInterval *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust min and max to ensure they are on the rounded interval
|
||||||
|
let adjustedMin = Math.floor(minY / roundedInterval) * roundedInterval;
|
||||||
|
let adjustedMax = Math.ceil(maxY / roundedInterval) * roundedInterval;
|
||||||
|
|
||||||
|
let first = data[0];
|
||||||
|
let last = data[data.length - 1];
|
||||||
|
return {
|
||||||
|
min: adjustedMin,
|
||||||
|
max: adjustedMax,
|
||||||
|
interval: roundedInterval,
|
||||||
|
first: first,
|
||||||
|
last: last,
|
||||||
|
rawMin: minY,
|
||||||
|
rawMax: maxY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
{ "id": "coin_info",
|
||||||
|
"name": "Crypto-Coins Info",
|
||||||
|
"shortName":"Coins Info",
|
||||||
|
"version": "0.10",
|
||||||
|
"description": "Crypto-Coins Infos with the help of the Binance API",
|
||||||
|
"icon": "app.png",
|
||||||
|
"tags": "clkinfo",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"interface": "interface.html",
|
||||||
|
"readme": "README.md",
|
||||||
|
"data": [
|
||||||
|
{"name":"coin_info.settings.json"},
|
||||||
|
{"name":"coin_info.cmc_key.json"},
|
||||||
|
{"name":"coin_info.coin_info_log.txt"}
|
||||||
|
],
|
||||||
|
"storage": [
|
||||||
|
{"name":"coin_info.app.js","url":"app.js"},
|
||||||
|
{"name":"coin_info.clkinfo.js","url":"clkinfo.js"},
|
||||||
|
{"name":"coin_info.settings.js","url":"settings.js"},
|
||||||
|
{"name":"coin_info.img","url":"app-icon.js","evaluate":true},
|
||||||
|
{"name":"coin_info","url":"lib.js"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
|
@ -0,0 +1,53 @@
|
||||||
|
(function(back) {
|
||||||
|
const SETTINGS_FILE = "coin_info.settings.json";
|
||||||
|
const storage = require('Storage');
|
||||||
|
|
||||||
|
// Default settings with sorted tokens and load settings
|
||||||
|
let settings = Object.assign({
|
||||||
|
// TODO: MZw - retrieve from upload-storage
|
||||||
|
tokens: ['BTC', 'ETH', 'STORJ'],
|
||||||
|
tokenSelected: ['BTC'],
|
||||||
|
getRateMin: 60
|
||||||
|
}, storage.readJSON(SETTINGS_FILE, 1) || {});
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
storage.write(SETTINGS_FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMenu() {
|
||||||
|
const menu = {
|
||||||
|
'': { 'title': 'Crypto-Coin Info' },
|
||||||
|
'< Back': () => Bangle.showClock()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic checkbox creation
|
||||||
|
settings.tokens.sort().forEach(token => {
|
||||||
|
menu[token] = {
|
||||||
|
value: settings.tokenSelected.includes(token),
|
||||||
|
onchange: v => {
|
||||||
|
if (v) {
|
||||||
|
settings.tokenSelected.push(token);
|
||||||
|
} else {
|
||||||
|
settings.tokenSelected = settings.tokenSelected.filter(f => f !== token);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// update time
|
||||||
|
menu['Refresh Rate (min)'] = {
|
||||||
|
value: settings.getRateMin,
|
||||||
|
min: 1,
|
||||||
|
max: 1440,
|
||||||
|
onchange: v => {
|
||||||
|
settings.getRateMin = v;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu(createMenu());
|
||||||
|
})
|
||||||