Skip to content

Commit 7a37ea3

Browse files
committed
fix(add_sqlite3_for_persistent_storage): adding sqlite3 for persistent storage between browsers.
-- fixes dump download archive to actually download archive and alert user to not navigate from page until archive finishes. -- adds a view current playlist dialog to assist user of firestorm to quickly see whats in the current playlist being sent to PixelBlazes -- adds brightness slider to Firestorm UI to control brightness on all PixelBlazes in network. -- add SQLite to backend, and send messages to backend via websockets to assist in user expereince verus using fetch to send requests to backend node-server. Node-server on backend will process playlist now. -- add Enable/Disable All buttons to quickly enable all patterns in Firestorm list so a user doesn't have to click through them one by one if they dont want to. -- minor styling issues to better use the bootstrap templating. -- upgraded webstorm to 8.13.0 and modified controller to look at binary messages due to spec changes
1 parent 2d8fcac commit 7a37ea3

File tree

17 files changed

+1955
-118
lines changed

17 files changed

+1955
-118
lines changed

app/brightness.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const _ = require("lodash");
2+
const {updateBrightness, getCurrentBrightness} = require("../db/controllers/brightness");
3+
const {discoverPixelBlazes, sendCommand} = require("./pixelBlazeUtils");
4+
5+
let currentBrightness
6+
let pixelBlazeData = []
7+
let pixelBlazeIds = []
8+
init = async () => {
9+
getCurrentBrightness()
10+
.then((brightness) => {
11+
try {
12+
currentBrightness = brightness[0].value
13+
} catch (err) {
14+
console.warn(`Error: ${err}`)
15+
}
16+
})
17+
pixelBlazeData = discoverPixelBlazes()
18+
pixelBlazeIds = _.map(pixelBlazeData, 'id')
19+
}
20+
21+
initInterval = setInterval(init, 100)
22+
23+
class Brightness {
24+
constructor(utils) {
25+
this.utils = utils ? utils : null
26+
}
27+
adjustBrightness = async (brightness) => {
28+
await new Promise((resolve) => {
29+
const tempBrightness = (brightness) ? brightness : currentBrightness
30+
this.delayedSaveBrightness(resolve, tempBrightness)
31+
})
32+
}
33+
delayedSaveBrightness = _.debounce(async (resolve, brightness) => {
34+
sendCommand(pixelBlazeIds, null, brightness)
35+
await this.storeBrightness(brightness);
36+
currentBrightness = brightness
37+
await this.sendBrightnessMessage(currentBrightness)
38+
}, 1000)
39+
getBrightness = async () =>{
40+
await this.sendBrightnessMessage(currentBrightness)
41+
}
42+
storeBrightness = async (brightness) => {
43+
const body = {
44+
value: brightness
45+
}
46+
await updateBrightness(body)
47+
}
48+
sendBrightnessMessage = async (currentBrightness) => {
49+
// skipping this if utils is not initialized due to no websocket connections
50+
if (this.utils) {
51+
await this.utils.broadcastMessage({currentBrightness: currentBrightness})
52+
}
53+
}
54+
}
55+
// Initializing the brightness loop outside the websocket
56+
// because we might not always have a browser open when
57+
// starting/restarting the node-server... it should send
58+
// commands and operate on the brightness w/o the need of an
59+
// active websocket connection
60+
initThis = async () => {
61+
// halting the brightness message until we get it from the db
62+
while (currentBrightness === undefined) {
63+
await new Promise(resolve => {
64+
setTimeout(resolve, 100)
65+
})
66+
}
67+
let initThe = new Brightness()
68+
await initThe.adjustBrightness(currentBrightness)
69+
}
70+
initThis().then(()=>{})
71+
72+
73+
module.exports.BrightnessWebsocketMessageHandler = function (utils) {
74+
const brightness = new Brightness(utils)
75+
this.utils = utils
76+
77+
this.receiveMessage = async function (data) {
78+
let message
79+
try {
80+
message = JSON.parse(data);
81+
} catch (err) {
82+
this.utils.sendError(err)
83+
return
84+
}
85+
if (message.type === 'ADJUST_BRIGHTNESS') {
86+
// console.log('received adjust brightness message!')
87+
await brightness.adjustBrightness(parseFloat(message.brightness))
88+
}
89+
if (message.type === 'GET_CURRENT_BRIGHTNESS') {
90+
// console.log('received get current brightness message!')
91+
await brightness.getBrightness()
92+
}
93+
}
94+
}

app/controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ module.exports = class PixelblazeController {
103103
this.reconectTimeout = setTimeout(this.connect, 1000);
104104
}
105105

106-
handleMessage(msg) {
106+
handleMessage(msg, isBinary) {
107107
this.lastSeen = new Date().getTime();
108108
// console.log("data from " + this.props.id + " at " + this.props.address, typeof msg, msg);
109109

110110
let props = this.props;
111-
if (typeof msg === "string") {
111+
if (!isBinary) {
112112
try {
113113
_.assign(this.props, _.pick(JSON.parse(msg), PROPFIELDS));
114114
} catch (err) {

app/firestormWebsocket.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const WebSocketServer = require('ws').Server
2+
const {PlaylistWebSocketMessageHandler} = require('./playlist')
3+
const {Utils} = require('./utils')
4+
const {BrightnessWebsocketMessageHandler} = require("./brightness");
5+
6+
// start FireStorm WebSocket server
7+
const address = '0.0.0.0';
8+
const port = 1890;
9+
const firestormServer = new WebSocketServer({host: address , port: port});
10+
console.log(`Firestorm server is running on ${address}:${port}`);
11+
12+
firestormServer.on('connection', function (connection) {
13+
const utils = new Utils(connection)
14+
const brightnessWebsocketMessageHandler = new BrightnessWebsocketMessageHandler(utils)
15+
const playlistWebSocketMessageHandler = new PlaylistWebSocketMessageHandler(utils)
16+
if(utils.addFirestormClient(connection)) {
17+
return
18+
}
19+
connection.on('message', async function message(data, isBinary) {
20+
const message = isBinary ? data : data.toString();
21+
// console.log(`incoming msg from: ${utils.getFirestormClientBySocket(connection)}, message: ${message}`)
22+
if (await playlistWebSocketMessageHandler.receiveMessage(message)) {
23+
return
24+
}
25+
if (await brightnessWebsocketMessageHandler.receiveMessage(message)) {
26+
return
27+
}
28+
})
29+
connection.on('close', function() {
30+
console.log('closed connection')
31+
})
32+
})

app/pixelBlazeUtils.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const _ = require("lodash");
2+
const {discoveries} = require("./discovery");
3+
4+
module.exports.discoverPixelBlazes = () => {
5+
return _.map(discoveries, function (v, k) {
6+
let res = _.pick(v, ['lastSeen', 'address']);
7+
_.assign(res, v.controller.props);
8+
return res;
9+
})
10+
}
11+
12+
module.exports.sendCommand = (pixelBlazeIds, name, brightness) => {
13+
_.each(pixelBlazeIds, async id => {
14+
id = String(id);
15+
let controller = discoveries[id] && discoveries[id].controller;
16+
if (controller) {
17+
let command = null
18+
if(name !== null && name !== undefined) {
19+
command = {
20+
programName: name
21+
}
22+
}
23+
if(brightness !== null && brightness !== undefined){
24+
command = {
25+
brightness: brightness
26+
}
27+
}
28+
if (command) {
29+
await controller.setCommand(command);
30+
} else {
31+
console.log(`No command sent to Pixelblazes command is ${command}`)
32+
}
33+
}
34+
})
35+
}

app/playlist.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
const _ = require("lodash");
2+
const {getPlaylistFromDB, addPatternToPlaylist, removeAllPatterns} = require("../db/controllers/playlist");
3+
const {discoverPixelBlazes, sendCommand} = require("./pixelBlazeUtils");
4+
5+
let currentPlaylist = []
6+
let currentRunningPattern = null
7+
let initInterval
8+
let pixelBlazeData = []
9+
let pixelBlazeIds = []
10+
let playlistLoopTimeout
11+
let playlistTimeout
12+
13+
init = async () => {
14+
getPlaylistFromDB()
15+
.then((data) => {
16+
try {
17+
currentPlaylist = [] // resetting current playlist so it doesn't grow to infinity
18+
currentPlaylist.push(...data) // adding new playlist items to list
19+
} catch (err) {
20+
console.warn(`Error: ${err}`)
21+
}
22+
})
23+
.catch('there was an error gathering playlist details')
24+
25+
// gather pixelBlaze data
26+
pixelBlazeData = discoverPixelBlazes()
27+
pixelBlazeIds = _.map(pixelBlazeData, 'id')
28+
}
29+
30+
initInterval = setInterval(init, 100)
31+
32+
class Playlist {
33+
constructor(utils) {
34+
this.utils = utils ? utils : null
35+
}
36+
37+
playlistLoop = async () => {
38+
while(true) {
39+
await new Promise(resolve => {
40+
playlistLoopTimeout = setTimeout(resolve, 100)
41+
});
42+
if(pixelBlazeIds.length) {
43+
await this.iterateOnPlaylist()
44+
}
45+
initInterval = null
46+
playlistLoopTimeout = null
47+
playlistTimeout = null
48+
}
49+
}
50+
iterateOnPlaylist = async () => {
51+
for (let index = 0; index < currentPlaylist.length; index++) {
52+
const pattern = currentPlaylist[index]
53+
await this.delaySendPattern(pattern)
54+
await new Promise(resolve => {
55+
playlistTimeout = setTimeout(resolve, pattern.duration * 1000)
56+
});
57+
}
58+
}
59+
delaySendPattern = async (pattern) => {
60+
await new Promise((resolve) => {
61+
resolve(
62+
this.sendPattern(pattern)
63+
)
64+
})
65+
}
66+
disableAllPatterns = async () => {
67+
await removeAllPatterns()
68+
await this.runPlaylistLoopNow()
69+
}
70+
enableAllPatterns = async (duration) => {
71+
const pixelBlazePatterns = this.gatherPatternData(pixelBlazeData)
72+
const enableAll = new Promise((resolve) => {
73+
_.each(pixelBlazePatterns, pattern => {
74+
pattern['duration'] = duration
75+
let body = {
76+
name: pattern.name,
77+
duration: pattern.duration
78+
}
79+
addPatternToPlaylist(body)
80+
})
81+
resolve();
82+
});
83+
enableAll
84+
.then(() => {
85+
this.runPlaylistLoopNow()
86+
})
87+
}
88+
gatherPatternData = (pixelBlazeData) => {
89+
let groupByPatternName = {};
90+
_.each(pixelBlazeData, d => {
91+
d.name = d.name || "Pixelblaze_" + d.id // set name if missing
92+
_.each(d.programList, p => {
93+
let pb = {
94+
id: d.id,
95+
name: d.name
96+
};
97+
if (groupByPatternName[p.name]) {
98+
groupByPatternName[p.name].push(pb);
99+
} else {
100+
groupByPatternName[p.name] = [pb];
101+
}
102+
})
103+
})
104+
let groups = _.chain(groupByPatternName)
105+
.map((v, k) => ({name: k}))
106+
.sortBy('name')
107+
.value();
108+
return groups
109+
}
110+
getCurrentProgramState = async () => {
111+
let message = {
112+
currentRunningPattern: currentRunningPattern,
113+
currentPlaylist: currentPlaylist
114+
}
115+
await this.sendPlaylistMessage(message)
116+
}
117+
runPlaylistLoopNow = async () => {
118+
clearInterval(initInterval)
119+
clearInterval(playlistTimeout)
120+
clearInterval(playlistLoopTimeout)
121+
122+
await this.playlistLoop()
123+
}
124+
sendPattern = async (pattern) => {
125+
const name = pattern.name
126+
currentRunningPattern = name
127+
sendCommand(pixelBlazeIds, name)
128+
let message = {
129+
currentRunningPattern: name,
130+
currentPlaylist: currentPlaylist
131+
}
132+
await this.sendPlaylistMessage(message)
133+
}
134+
sendPlaylistMessage = async (message) => {
135+
// skipping this if utils is not initialized due to no websocket connections
136+
if(this.utils) {
137+
this.utils.broadcastMessage(message)
138+
}
139+
}
140+
141+
}
142+
// Initializing the playlist loop outside the websocket
143+
// because we might not always have a browser open when
144+
// starting/restarting the node-server... it should send
145+
// commands and operate on the playlist w/o the need of an
146+
// active websocket connection
147+
initThe = new Playlist()
148+
initThe.playlistLoop()
149+
.then(() => {})
150+
151+
152+
module.exports.PlaylistWebSocketMessageHandler = function (utils) {
153+
const playlist = new Playlist(utils)
154+
this.utils = utils
155+
156+
this.receiveMessage = async function (data) {
157+
let message
158+
try {
159+
message = JSON.parse(data);
160+
} catch (err) {
161+
this.utils.sendError(err)
162+
return
163+
}
164+
if (message.type === 'DISABLE_ALL_PATTERNS') {
165+
// console.log('received message to disable all patterns!')
166+
await playlist.disableAllPatterns(message.duration)
167+
}
168+
if (message.type === 'ENABLE_ALL_PATTERNS') {
169+
// console.log('received message to enable all patterns!')
170+
await playlist.enableAllPatterns(message.duration)
171+
}
172+
if (message.type === 'GET_CURRENT_PROGRAM_STATE') {
173+
// console.log('received get current program state message!')
174+
await playlist.getCurrentProgramState()
175+
}
176+
if (message.type === 'LAUNCH_PLAYLIST_NOW') {
177+
// console.log('received launch playlist now message!')
178+
await playlist.runPlaylistLoopNow()
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)