-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlcdDisplay.js
230 lines (165 loc) · 6.18 KB
/
lcdDisplay.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
'use strict';
var COLS,ROWS,RS,E,D4,D5,D6,D7;
var SCROLL_SPEED = 500;
var Lcd = require('lcd');
module.exports = lcdDisplay;
function lcdDisplay(context,config) {
var self = this;
self.displayTimer = undefined;
self.currentState = undefined;
self.scrollPos = 0;
self.elapsed = 0;
// Set up logger
self.context = context;
self.logger = self.context.logger;
// Get configuration parameters
COLS = config.get('COLS'); ROWS = config.get('ROWS');
RS = config.get('RS'); E = config.get('E');
D4 = config.get('D4'); D5 = config.get('D5');
D6 = config.get('D6'); D7 = config.get('D7');
self.lcd = new Lcd({rs: RS, e: E, data: [D4, D5, D6, D7], cols: COLS, rows: ROWS});
self.lcd.on('error',function(err) { // Handle any errors we forget about so they don't crash Node
self.logger.error('[lcdHD47780] LCD Error: ' + err);
});
self.lcd.on('ready',function() {
self.logger.info(`[lcdhd47780] LCD Ready COLS=${COLS} ROWS=${ROWS} RS=${RS} E=${E} D4=${D4} D5=${D5} D6=${D6} D7=${D7}`);
});
}
lcdDisplay.prototype.close = function() {
var self = this;
if (self.displayTimer !== undefined) {
clearInterval(self.displayTimer); // Stop Timer
}
self.lcd.clear(function(err) { // Clear first before close so everything is tidy
if(err) {
self.logger.error('[lcdHD47780] LCD Error: ' + err);
}
else {
self.lcd.close();
}
});
};
lcdDisplay.prototype.pushState = function(state) {
// TODO: Needs some redesign.
// If the state has just been pushed, we should update the LCD straight away
// rather than wait for the next interval
// Also we should be resetting the scrollPos if the track/title has changed
var self = this;
self.logger.info('[lcdHD47780] Recieved pushstate');
if(self.currentState){
if(self._formatTrackInfo(self.currentState)!==self._formatTrackInfo(state)){
self.scrollPos = 0;
}
}
if(self.displayTimer === undefined){
self.displayTimer = setInterval( self.updateLCD.bind(self),SCROLL_SPEED);
self.logger.info('[lcdHD47780] Set up display Timer');
}
self.elapsed = state.seek;
self.currentState = state; // Update state
self.logger.info('[lcdHD47780] Processed pushstate');
};
lcdDisplay.prototype.updateLCD = function() {
var self = this;
if(self.currentState!==undefined) {
// Track Information
var trackInfo = self._formatTrackInfo(self.currentState);
if (self.scrollPos >= trackInfo.length)
self.scrollPos = 0; // Reset scroll
trackInfo = self._formatTextForScrolling(trackInfo,self.scrollPos,COLS);
// Source / Elapsed / Duration
var duration = self.currentState.duration;
// If it's a WebRadio then don't show elapsed/duration
var elapsedInfo = (self.currentState.service==='webradio') ?
self._padEnd('WebRadio',COLS) :
self._formatSeekDuration(self.elapsed,duration);
self.lcd.setCursor(0,0);
// Print track info
self.lcd.print(trackInfo,function (err) {
self.scrollPos++; // Advance scroll position
// Track info printed ok, set lets print elapsed / duration
self.lcd.setCursor(0,1);
self.lcd.print(elapsedInfo,function (err) {
if (self.currentState.status === 'play')
self.elapsed += SCROLL_SPEED; // Advanced elapsed if playing
});
});
}
};
/**
* In some cases (webradio) only the artist or the title is actually populated
* so we need to check what is populated
* Also if we don't have that info or we aren't playing / paused then just display
* a placeholder
* @param {Object} data - Current state
* @private
*/
lcdDisplay.prototype._formatTrackInfo = function(data) {
var self = this;
var txt = "";
if(!data.artist && !data.title) { // TODO: Check this, would we want to display uri in this case?
txt = 'No track data';
} else {
if (data.artist)
txt = data.artist;
if(data.title)
txt += txt ? (' - ' + data.title) : data.title;
}
// If the text length is less than or equal to the lcd width then
// just pad with spaces and don't scroll
if(txt.length <= COLS)
txt += (' ').repeat(COLS - txt.length);
else
txt += (' ').repeat(COLS/2); // Add some spaces so it doesn't look naff if it's scrolling
return txt;
};
/**
* Take in the track info, scroll position and width of the lcd and return the text to display
* @param {String} trackInfo - Track name / artist
* @param {Integer} pos - Current scroll position
* @param {Integer} lcdWidth - Character width of lcd
* @private
*/
lcdDisplay.prototype._formatTextForScrolling = function(trackInfo,pos,lcdWidth){
if (trackInfo.length==lcdWidth)
return trackInfo;
else
return ((trackInfo.substr(pos) + trackInfo.substr(0, pos)).substr(0,lcdWidth));
};
/**
* Formats the seek and duration into a text format suitable for display
* @param {Integer} seek - Seek time in milliseconds
* @param {Integer} duration - Duration time in seconds
* @private
*/
lcdDisplay.prototype._formatSeekDuration = function(seek, duration) {
var self = this;
var seekSec = Math.floor(seek / 1000); // convert seek to seconds
var seekMin = Math.floor(seekSec / 60); // calculate whole seek minutes
seekSec = seekSec - (seekMin * 60); // remaining seconds
seekMin = seekMin % 100; // only two digits for minutes, so wrap back to 0 once we hit 100
if (seekMin < 10) (seekMin = "0" + seekMin); // pad minutes
if (seekSec < 10) (seekSec = "0" + seekSec); // pad seconds
var txt = seekMin + ":" + seekSec;
if (duration) {
var durMin = Math.floor(duration / 60); // calculate whole duration minutes
var durSec = duration % 60; // remaining seconds
durMin = durMin % 100; // only two digits for minutes, so wrap back to 0 once we hit 100
if (durMin < 10) (durMin = "0" + durMin); // pad minutes
if (durSec < 10) (durSec = "0" + durSec); // pad seconds
txt+= " / " + durMin + ":" + durSec; // add duration to display
}
return self._padEnd(txt,COLS);
};
/**
* Volumio runs on a version of Node that doesn't support padEnd, so just use our own implementation
* @param {string} string - String to pad
* @param {Integer} length - String length to pad to
* @private
*/
lcdDisplay.prototype._padEnd = function(string,length) {
if(string.length>=length)
return string;
else
return (string + " ".repeat(length - string.length));
};