Ability to auto-discover sonos devices or find by using a seed ip

This commit is contained in:
simojenki
2021-01-29 16:54:38 +11:00
parent 45fe62841f
commit 5cd98a5ea6
12 changed files with 2881 additions and 578 deletions

View File

@@ -1,4 +0,0 @@
{
"require": "./register.js",
"reporter": "dot"
}

View File

@@ -1,17 +0,0 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules/"
],
"extension": [
".ts"
],
"reporter": [
"text-summary",
"html"
],
"report-dir": "./coverage"
}

4
jest.config.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

View File

View File

@@ -18,19 +18,21 @@
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.2.14", "@types/chai": "^4.2.14",
"@types/jest": "^26.0.20",
"@types/mocha": "^8.2.0", "@types/mocha": "^8.2.0",
"@types/supertest": "^2.0.10",
"chai": "^4.2.0", "chai": "^4.2.0",
"mocha": "^8.2.1", "jest": "^26.6.3",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"nyc": "^15.1.0", "supertest": "^6.1.3",
"ts-jest": "^26.4.4",
"ts-mockito": "^2.6.1", "ts-mockito": "^2.6.1",
"ts-node": "^9.1.1" "ts-node": "^9.1.1"
}, },
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node ./build/app.js",
"dev": "nodemon ./src/app.ts", "dev": "nodemon ./src/app.ts",
"test": "nyc ./node_modules/.bin/_mocha 'tests/**/*.test.ts'", "test": "jest",
"testw": "mocha --require ts-node/register --watch --watch-files tests/**/* tests/**/*.test.ts" "testw": "mocha --require ts-node/register --watch --watch-files tests/**/* tests/**/*.test.ts"
} }
} }

View File

@@ -1,26 +1,12 @@
import express from "express"; import sonos from "./sonos";
import * as Eta from "eta"; import server from "./server";
// import { Navidrome } from "./music_service"; const PORT = process.env["PORT"] || 3000;
import makeSonos from "./sonos";
const PORT = 3000; const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]));
makeSonos().then((sonos) => { app.listen(PORT, () => {
const app = express(); console.info(`Listening on ${PORT}`);
app.use(express.static("./web/public"));
app.engine("eta", Eta.renderFile);
app.set("view engine", "eta");
app.set("views", "./web/views");
app.get("/", (_, res) => {
res.render("index", {
devices: sonos.devices(),
});
});
app.listen(PORT, () => {
console.info(`Listening on ${PORT}`);
});
}); });
export default app;

22
src/server.ts Normal file
View File

@@ -0,0 +1,22 @@
import express, { Express } from "express";
import * as Eta from "eta";
import { Sonos } from "./sonos";
function server(sonos: Sonos): Express {
const app = express();
app.use(express.static("./web/public"));
app.engine("eta", Eta.renderFile);
app.set("view engine", "eta");
app.set("views", "./web/views");
app.get("/", (_, res) => {
res.render("index", {
devices: sonos.devices(),
});
});
return app;
}
export default server;

View File

@@ -1,5 +1,4 @@
import { SonosManager } from "@svrooij/sonos"; import { SonosManager, SonosDevice } from "@svrooij/sonos";
import logger from "./logger"; import logger from "./logger";
type Device = { type Device = {
@@ -9,40 +8,49 @@ type Device = {
port: number; port: number;
}; };
interface Sonos export interface Sonos {
{
devices: () => Device[]; devices: () => Device[];
} }
class RealSonos implements Sonos { export const SONOS_DISABLED: Sonos = {
manager: SonosManager;
constructor(manager: SonosManager) {
this.manager = manager;
}
devices = (): Device[] => {
const devices = this.manager.Devices.map((d) => ({
name: d.Name,
group: d.GroupName || "",
ip: d.Host,
port: d.Port,
}));
logger.debug({ devices })
return devices;
}
}
const SonosDisabled: Sonos = {
devices: () => [], devices: () => [],
}; };
export default function (): Promise<Sonos> { const asDevice = (sonosDevice: SonosDevice) => ({
name: sonosDevice.Name,
group: sonosDevice.GroupName || "",
ip: sonosDevice.Host,
port: sonosDevice.Port,
});
const setupDiscovery = (manager: SonosManager, sonosSeedHost?: string): Promise<boolean> => {
if (sonosSeedHost == undefined || sonosSeedHost == "") {
logger.info("Trying to auto discover sonos devices");
return manager.InitializeWithDiscovery(10);
} else {
logger.info(`Trying to discover sonos devices using seed ${sonosSeedHost}`);
return manager.InitializeFromDevice(sonosSeedHost);
}
};
export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
const manager = new SonosManager(); const manager = new SonosManager();
return manager
.InitializeWithDiscovery(10) setupDiscovery(manager, sonosSeedHost).then((r) => {
.then((it) => (it ? new RealSonos(manager) : SonosDisabled)) if (r) logger.info({ devices: manager.Devices.map(asDevice) });
.catch((_) => { else logger.warn("Failed to auto discover hosts!");
return SonosDisabled; });
});
return {
devices: () => manager.Devices.map(asDevice),
};
}
export default function sonos(sonosSeedHost?: string): Sonos {
switch (sonosSeedHost) {
case "disabled":
return SONOS_DISABLED;
default:
return autoDiscoverySonos(sonosSeedHost);
}
} }

View File

@@ -1,7 +0,0 @@
import { expect } from "chai";
describe("something", () => {
it("fails", () => {
expect(true).equal(true);
});
});

46
tests/index.test.ts Normal file
View File

@@ -0,0 +1,46 @@
import request from "supertest";
import makeServer from "../src/server";
import { SONOS_DISABLED, Sonos } from "../src/sonos";
describe("index", () => {
describe("when sonos integration is disabled", () => {
const server = makeServer(SONOS_DISABLED);
describe("devices list", () => {
it("should be empty", async () => {
const res = await request(server).get("/").send();
expect(res.status).toEqual(200);
expect(res.text).not.toMatch(/class=device/)
});
});
});
describe("when sonos integration is enabled", () => {
const fakeSonos: Sonos = {
devices: () => [{
name: "device1",
group: "group1",
ip: "172.0.0.1",
port: 4301
},{
name: "device2",
group: "group2",
ip: "172.0.0.2",
port: 4302
}]
}
const server = makeServer(fakeSonos);
describe("devices list", () => {
it("should contain the devices returned from sonos", async () => {
const res = await request(server).get("/").send();
expect(res.status).toEqual(200);
expect(res.text).toMatch(/device1\s+\(172.0.0.1:4301\)/)
expect(res.text).toMatch(/device2\s+\(172.0.0.2:4302\)/)
});
});
});
});

View File

@@ -1,6 +1,6 @@
<ul> <ul>
<% it.devices.forEach(function(d){ %> <% it.devices.forEach(function(d){ %>
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li> <li class="device"><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
<% }) %> <% }) %>
</ul> </ul>

3247
yarn.lock

File diff suppressed because it is too large Load Diff