mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
betterneato
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||||
<title>Multi Roomba Rover</title>
|
<title>Multi Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DIzOCfJB.js"></script>
|
<script type="module" crossorigin src="/assets/index-CKRKhxHH.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CQrwYoqn.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-EE3dCtON.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function normalizeDeviceName(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const device = normalizeDeviceName(neatoConfig.device);
|
const device = normalizeDeviceName(neatoConfig.device);
|
||||||
|
const RESUME_DELAY_MS = 3000;
|
||||||
|
|
||||||
function entityId(domain, suffix) {
|
function entityId(domain, suffix) {
|
||||||
if (!device) return '';
|
if (!device) return '';
|
||||||
@@ -35,9 +36,11 @@ function entityId(domain, suffix) {
|
|||||||
const ENTITY_IDS = {
|
const ENTITY_IDS = {
|
||||||
buttons: {
|
buttons: {
|
||||||
start: entityId('button', 'house_clean'),
|
start: entityId('button', 'house_clean'),
|
||||||
|
resume: entityId('button', 'resume_cleaning'),
|
||||||
sendHome: entityId('button', 'send_to_base'),
|
sendHome: entityId('button', 'send_to_base'),
|
||||||
locate: entityId('button', 'locate_robot'),
|
locate: entityId('button', 'locate_robot'),
|
||||||
clearErrors: entityId('button', 'clear_errors'),
|
clearErrors: entityId('button', 'clear_errors'),
|
||||||
|
powerCycle: entityId('button', 'powercycle'),
|
||||||
},
|
},
|
||||||
sensors: {
|
sensors: {
|
||||||
batteryPercent: entityId('sensor', 'fuel_percent'),
|
batteryPercent: entityId('sensor', 'fuel_percent'),
|
||||||
@@ -90,9 +93,11 @@ function isEntityAvailable(entityIdValue) {
|
|||||||
function requiredEntityIds() {
|
function requiredEntityIds() {
|
||||||
return [
|
return [
|
||||||
ENTITY_IDS.buttons.start,
|
ENTITY_IDS.buttons.start,
|
||||||
|
ENTITY_IDS.buttons.resume,
|
||||||
ENTITY_IDS.buttons.sendHome,
|
ENTITY_IDS.buttons.sendHome,
|
||||||
ENTITY_IDS.buttons.locate,
|
ENTITY_IDS.buttons.locate,
|
||||||
ENTITY_IDS.buttons.clearErrors,
|
ENTITY_IDS.buttons.clearErrors,
|
||||||
|
ENTITY_IDS.buttons.powerCycle,
|
||||||
ENTITY_IDS.sensors.batteryPercent,
|
ENTITY_IDS.sensors.batteryPercent,
|
||||||
ENTITY_IDS.sensors.batteryVoltage,
|
ENTITY_IDS.sensors.batteryVoltage,
|
||||||
ENTITY_IDS.binarySensors.chargingActive,
|
ENTITY_IDS.binarySensors.chargingActive,
|
||||||
@@ -117,6 +122,10 @@ function buildState() {
|
|||||||
entityId: ENTITY_IDS.buttons.start,
|
entityId: ENTITY_IDS.buttons.start,
|
||||||
available: hasEntity(ENTITY_IDS.buttons.start),
|
available: hasEntity(ENTITY_IDS.buttons.start),
|
||||||
},
|
},
|
||||||
|
resume: {
|
||||||
|
entityId: ENTITY_IDS.buttons.resume,
|
||||||
|
available: hasEntity(ENTITY_IDS.buttons.resume),
|
||||||
|
},
|
||||||
sendHome: {
|
sendHome: {
|
||||||
entityId: ENTITY_IDS.buttons.sendHome,
|
entityId: ENTITY_IDS.buttons.sendHome,
|
||||||
available: hasEntity(ENTITY_IDS.buttons.sendHome),
|
available: hasEntity(ENTITY_IDS.buttons.sendHome),
|
||||||
@@ -129,6 +138,10 @@ function buildState() {
|
|||||||
entityId: ENTITY_IDS.buttons.clearErrors,
|
entityId: ENTITY_IDS.buttons.clearErrors,
|
||||||
available: hasEntity(ENTITY_IDS.buttons.clearErrors),
|
available: hasEntity(ENTITY_IDS.buttons.clearErrors),
|
||||||
},
|
},
|
||||||
|
powerCycle: {
|
||||||
|
entityId: ENTITY_IDS.buttons.powerCycle,
|
||||||
|
available: hasEntity(ENTITY_IDS.buttons.powerCycle),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const batteryPercentValue = parseNumber(readState(ENTITY_IDS.sensors.batteryPercent));
|
const batteryPercentValue = parseNumber(readState(ENTITY_IDS.sensors.batteryPercent));
|
||||||
@@ -208,6 +221,8 @@ async function pressButton(entityIdValue, actionLabel) {
|
|||||||
|
|
||||||
async function startCleaning() {
|
async function startCleaning() {
|
||||||
await pressButton(ENTITY_IDS.buttons.start, 'start');
|
await pressButton(ENTITY_IDS.buttons.start, 'start');
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, RESUME_DELAY_MS));
|
||||||
|
await pressButton(ENTITY_IDS.buttons.resume, 'resume_cleaning');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendHome() {
|
async function sendHome() {
|
||||||
@@ -222,6 +237,10 @@ async function clearErrors() {
|
|||||||
await pressButton(ENTITY_IDS.buttons.clearErrors, 'clear_errors');
|
await pressButton(ENTITY_IDS.buttons.clearErrors, 'clear_errors');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function powerCycle() {
|
||||||
|
await pressButton(ENTITY_IDS.buttons.powerCycle, 'powercucle');
|
||||||
|
}
|
||||||
|
|
||||||
function getState() {
|
function getState() {
|
||||||
cachedState = buildState();
|
cachedState = buildState();
|
||||||
return cachedState;
|
return cachedState;
|
||||||
@@ -275,6 +294,18 @@ io.on('connection', (socket) => {
|
|||||||
cb({ error: err.message });
|
cb({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('neato:powerCycle', async (_, cb = () => {}) => {
|
||||||
|
try {
|
||||||
|
if (!isVerified(socket)) {
|
||||||
|
throw new Error('VIP verification required');
|
||||||
|
}
|
||||||
|
await powerCycle();
|
||||||
|
cb({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
cb({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
emitUpdate();
|
emitUpdate();
|
||||||
@@ -285,5 +316,6 @@ module.exports = {
|
|||||||
sendHome,
|
sendHome,
|
||||||
locateRobot,
|
locateRobot,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
|
powerCycle,
|
||||||
neatoEvents: events,
|
neatoEvents: events,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default function VipPanel() {
|
|||||||
neatoSendHome,
|
neatoSendHome,
|
||||||
neatoLocate,
|
neatoLocate,
|
||||||
neatoClearErrors,
|
neatoClearErrors,
|
||||||
|
neatoPowerCycle,
|
||||||
liftUp,
|
liftUp,
|
||||||
liftDown,
|
liftDown,
|
||||||
} = useSession();
|
} = useSession();
|
||||||
@@ -81,6 +82,7 @@ export default function VipPanel() {
|
|||||||
onSendHome={neatoSendHome}
|
onSendHome={neatoSendHome}
|
||||||
onLocate={neatoLocate}
|
onLocate={neatoLocate}
|
||||||
onClearErrors={neatoClearErrors}
|
onClearErrors={neatoClearErrors}
|
||||||
|
onPowerCycle={neatoPowerCycle}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<VipLiftCard
|
<VipLiftCard
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default function VipNeatoCard({
|
|||||||
onSendHome,
|
onSendHome,
|
||||||
onLocate,
|
onLocate,
|
||||||
onClearErrors,
|
onClearErrors,
|
||||||
|
onPowerCycle,
|
||||||
fullWidth = false,
|
fullWidth = false,
|
||||||
}) {
|
}) {
|
||||||
const [working, setWorking] = useState('');
|
const [working, setWorking] = useState('');
|
||||||
@@ -71,11 +72,13 @@ export default function VipNeatoCard({
|
|||||||
const canSendHome = Boolean(controls?.sendHome?.available);
|
const canSendHome = Boolean(controls?.sendHome?.available);
|
||||||
const canLocate = Boolean(controls?.locate?.available);
|
const canLocate = Boolean(controls?.locate?.available);
|
||||||
const canClearErrors = Boolean(controls?.clearErrors?.available);
|
const canClearErrors = Boolean(controls?.clearErrors?.available);
|
||||||
|
const canPowerCycle = Boolean(controls?.powerCycle?.available);
|
||||||
|
|
||||||
const canRunStart = configured && connected && canStart;
|
const canRunStart = configured && connected && canStart;
|
||||||
const canRunSendHome = configured && connected && canSendHome;
|
const canRunSendHome = configured && connected && canSendHome;
|
||||||
const canRunLocate = configured && connected && canLocate;
|
const canRunLocate = configured && connected && canLocate;
|
||||||
const canRunClearErrors = configured && connected && canClearErrors;
|
const canRunClearErrors = configured && connected && canClearErrors;
|
||||||
|
const canRunPowerCycle = configured && connected && canPowerCycle;
|
||||||
|
|
||||||
const runAction = async (key, fn) => {
|
const runAction = async (key, fn) => {
|
||||||
if (!fn) return;
|
if (!fn) return;
|
||||||
@@ -131,7 +134,7 @@ export default function VipNeatoCard({
|
|||||||
>
|
>
|
||||||
{working === 'sendHome' ? 'Sending...' : 'Send to dock'}
|
{working === 'sendHome' ? 'Sending...' : 'Send to dock'}
|
||||||
</button>
|
</button>
|
||||||
<div className="grid grid-cols-2 gap-0.5">
|
<div className="grid grid-cols-3 gap-0.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!canRunLocate || Boolean(working)}
|
disabled={!canRunLocate || Boolean(working)}
|
||||||
@@ -148,6 +151,14 @@ export default function VipNeatoCard({
|
|||||||
>
|
>
|
||||||
{working === 'clearErrors' ? 'Clearing...' : 'Clear errors'}
|
{working === 'clearErrors' ? 'Clearing...' : 'Clear errors'}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={!canRunPowerCycle || Boolean(working)}
|
||||||
|
onClick={() => runAction('powerCycle', onPowerCycle)}
|
||||||
|
className="w-full rounded-md border border-rose-300 bg-rose-600 px-1 py-0.5 text-xs font-semibold text-white transition hover:border-rose-500 hover:bg-rose-500 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{working === 'powerCycle' ? 'Cycling...' : 'Power cycle'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="surface-muted grid gap-0.5 pt-0.25">
|
<div className="surface-muted grid gap-0.5 pt-0.25">
|
||||||
<p className="text-xs text-slate-300 text-center">Power status</p>
|
<p className="text-xs text-slate-300 text-center">Power status</p>
|
||||||
@@ -195,7 +206,7 @@ export default function VipNeatoCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!configured || !connected || !canStart || !canSendHome || !canLocate || !canClearErrors ? (
|
{!configured || !connected || !canStart || !canSendHome || !canLocate || !canClearErrors || !canPowerCycle ? (
|
||||||
<p className="text-xs text-slate-400 text-center">
|
<p className="text-xs text-slate-400 text-center">
|
||||||
{!configured
|
{!configured
|
||||||
? 'Set homeAssistant.neato.device in server config to enable Neato controls.'
|
? 'Set homeAssistant.neato.device in server config to enable Neato controls.'
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export function SessionProvider({ children }) {
|
|||||||
neatoSendHome: () => emitWithAck('neato:sendHome'),
|
neatoSendHome: () => emitWithAck('neato:sendHome'),
|
||||||
neatoLocate: () => emitWithAck('neato:locate'),
|
neatoLocate: () => emitWithAck('neato:locate'),
|
||||||
neatoClearErrors: () => emitWithAck('neato:clearErrors'),
|
neatoClearErrors: () => emitWithAck('neato:clearErrors'),
|
||||||
|
neatoPowerCycle: () => emitWithAck('neato:powerCycle'),
|
||||||
liftUp: () => emitWithAck('lift:up'),
|
liftUp: () => emitWithAck('lift:up'),
|
||||||
liftDown: () => emitWithAck('lift:down'),
|
liftDown: () => emitWithAck('lift:down'),
|
||||||
setNickname: (nickname) => emitWithAck('nickname:set', { nickname }),
|
setNickname: (nickname) => emitWithAck('nickname:set', { nickname }),
|
||||||
|
|||||||
Reference in New Issue
Block a user