Skip to content

Commit

Permalink
Working on controller
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanBluefox committed Nov 11, 2023
1 parent fe3f54c commit c5b6330
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 177 deletions.
1 change: 1 addition & 0 deletions io-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
],
"native": {
"interface": "",
"debug": false,
"login": "",
"pass": ""
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"@iobroker/adapter-core": "^3.0.4",
"@project-chip/matter-node.js": "0.6.1-alpha.0-20231110-2ec0f70",
"@project-chip/matter-node.js": "0.6.1-alpha.0-20231111-0f49576",
"@iobroker/type-detector": "^3.0.5",
"axios": "^1.6.1",
"jsonwebtoken": "^9.0.2"
Expand Down
3 changes: 2 additions & 1 deletion src-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@iobroker/adapter-react-v5": "^4.7.3",
"@iobroker/type-detector": "^3.0.5",
"@mui/icons-material": "^5.14.16",
"@mui/material": "5.14.14",
"@mui/styles": "5.14.14",
Expand All @@ -19,7 +20,7 @@
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"@iobroker/type-detector": "^3.0.5",
"qr-scanner": "^1.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0",
Expand Down
8 changes: 6 additions & 2 deletions src-admin/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@ class App extends GenericApp {
bridges: {},
devices: {},
};
this.state.detectedDevices = {};

this.state.detectedDevices = null;
this.configHandler = null;
this.intervalSubscribe = null;
this.alert = window.alert;
window.alert = text => this.showToast(text);

this.controllerMessageHandler = null;
}

refreshBackendSubscription() {
Expand Down Expand Up @@ -163,7 +165,7 @@ class App extends GenericApp {
} else if (update?.command === 'stopped') {
setTimeout(() => this.refreshBackendSubscription(), 5000);
} else {
console.log(`Unknown update: ${JSON.stringify(update)}`);
this.controllerMessageHandler && this.controllerMessageHandler(update);
}
};

Expand Down Expand Up @@ -201,6 +203,8 @@ class App extends GenericApp {
onChange={(id, value) => {
this.updateNativeValue(id, value);
}}
registerMessageHandler={handler => this.controllerMessageHandler = handler}
alive={this.state.alive}
socket={this.socket}
native={this.state.native}
themeType={this.state.themeType}
Expand Down
260 changes: 251 additions & 9 deletions src-admin/src/Tabs/Controller.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,256 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@mui/styles';
import QrScanner from 'qr-scanner';

import {
Switch,
Button, CircularProgress, Dialog,
DialogActions, DialogContent, DialogTitle, IconButton,
Switch, Table, TableBody,
TableCell, TableHead, TableRow, TextField,
LinearProgress, Select, MenuItem,
} from '@mui/material';
import {
Add, Close,
LeakAdd, Search,
} from '@mui/icons-material';

import { I18n } from '@iobroker/adapter-react-v5';

const styles = () => ({
address: {
fontSize: 'smaller',
opacity: 0.5,
marginLeft: 8,
},
panel: {
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: 8,
},
input: {
width: '100%',
maxWidth: 300,
qrScanner: {
width: 400,
height: 250,
},
});

class Controller extends React.Component {
constructor(props) {
super(props);
this.state = {
discovered: [],
discoveryRunning: false,
discoveryDone: false,
qrCode: null,
manualCode: '',
cameras: [],
camera: '',
hideVideo: false,
};

this.refQrScanner = React.createRef();
this.qrScanner = null;
}

componentDidMount() {
this.props.registerMessageHandler(this.onMessage);
}

componentWillUnmount() {
this.props.registerMessageHandler(null);
this.destroyQrCode();
}

async initQrCode() {
if (!this.qrScanner && this.refQrScanner.current) {
this.qrScanner = true;

this.qrScanner = new QrScanner(
this.refQrScanner.current,
result => {
if (result?.data && result.data !== this.state.qrCode) {
this.setState({ qrCode: result.data });
}
},
{
returnDetailedScanResult: true,
highlightCodeOutline: true,
maxScansPerSecond: 5,
// preferredCamera: camera,
},
);

const cameras = await QrScanner.listCameras(true);

const camera = window.localStorage.getItem('camera') || (cameras.length ? cameras[0].id : '');

await this.qrScanner.setCamera(camera);

this.setState({ cameras, camera, hideVideo: !cameras.length });
}

if (this.qrScanner && this.qrScanner !== true) {
await this.qrScanner.start();
}
}

onMessage = message => {
if (message?.command === 'discoveredDevice') {
const discovered = JSON.parse(JSON.stringify(this.state.discovered));
discovered.push(message.device);
this.setState({ discovered });
} else {
console.log(`Unknown update: ${JSON.stringify(message)}`);
}
};

destroyQrCode() {
if (this.qrScanner && this.qrScanner !== true) {
this.qrScanner.destroy();
}
this.qrScanner = null;
}

renderQrCodeDialog() {
if (!this.state.showQrCodeDialog) {
return null;
}
return <Dialog
open={!0}
onClose={() => this.setState({ showQrCodeDialog: false }, () => this.destroyQrCode())}
>
<DialogTitle>{I18n.t('QR Code')}</DialogTitle>
<DialogContent>
<TextField
variant="standard"
label={I18n.t('Manual pairing code')}
fullWidth
value={this.state.manualCode}
onChange={e => this.setState({ manualCode: e.target.value })}
/>
<TextField
variant="standard"
label={I18n.t('QR code')}
InputProps={{
readOnly: true,
}}
fullWidth
value={this.state.qrCode}
/>
{this.state.camera ? <br /> : null}
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video ref={this.refQrScanner} className={this.props.classes.qrScanner} style={{ display: this.state.hideVideo ? 'none' : 'block' }} />
{this.state.cameras.length ? <br /> : null}
{this.state.camera.length ? <Select
variant="standard"
value={this.state.camera}
onChange={async e => {
if (this.qrScanner && this.qrScanner !== true) {
await this.qrScanner.setCamera(e.target.value);
}
window.localStorage.setItem('camera', e.target.value);
this.setState({ camera: e.target.value });
}}
>
{this.state.cameras.map((camera, i) => <MenuItem key={i} value={camera.id}>{camera.label}</MenuItem>)}
</Select> : null}
</DialogContent>
<DialogActions>
<Button
variant="contained"
disabled={!this.state.qrCode && !this.state.manualCode}
color="primary"
onClick={() => {
const device = this.state.showQrCodeDialog;
this.setState({ showQrCodeDialog: false }, () => this.destroyQrCode());
this.props.socket.sendTo(`matter.${this.props.instance}`, 'controllerAddDevice', { device, qrCode: this.state.qrCode, manualCode: this.state.manualCode })
.then(result => {
if (result.error || !result.result) {
window.alert(`Cannot connect: ${result.error || 'Unknown error'}`);
} else {
window.alert('Connected');
}
});
}}
startIcon={<Add />}
>
{I18n.t('Add')}
</Button>
<Button
variant="contained"
color="grey"
onClick={() => this.setState({ showQrCodeDialog: false }, () => this.destroyQrCode())}
startIcon={<Close />}
>
{I18n.t('Close')}
</Button>
</DialogActions>
</Dialog>;
}

renderShowDiscoveredDevices() {
if (!this.state.discoveryRunning && !this.state.discoveryDone) {
return null;
}
return <Dialog
open={!0}
onClose={() => this.setState({ discoveryDone: false })}
>
<DialogTitle>{I18n.t('Discovered devices')}</DialogTitle>
<DialogContent>
{this.state.discoveryRunning ? <LinearProgress /> : null}
<Table style={{ width: '100%' }}>
<TableHead>
<TableRow>
<TableCell>
{I18n.t('Name')}
</TableCell>
<TableCell>
{I18n.t('Identifier')}
</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{this.state.discovered.map(device => <TableRow>
<TableCell>
{device.DN}
</TableCell>
<TableCell>
{device.deviceIdentifier}
</TableCell>
<TableCell>
<IconButton
onClick={() => {
this.setState({ showQrCodeDialog: device, manualCode: '', qrCode: '' });
setTimeout(async () => this.initQrCode(), 500);
}}
>
<LeakAdd />
</IconButton>
</TableCell>
</TableRow>)}
</TableBody>
</Table>
</DialogContent>
<DialogActions>
<Button
disabled={this.state.discoveryRunning}
variant="contained"
color="grey"
onClick={() => this.setState({ discoveryDone: false })}
startIcon={<Close />}
>
{I18n.t('Close')}
</Button>
</DialogActions>
</Dialog>;
}

render() {
return <div className={this.props.classes.panel}>
{this.renderShowDiscoveredDevices()}
{this.renderQrCodeDialog()}
<div>
{I18n.t('Off')}
<Switch
disabled={this.state.discoveryRunning}
checked={this.props.matter.controller.enabled}
onChange={e => {
const matter = JSON.parse(JSON.stringify(this.props.matter));
Expand All @@ -41,13 +260,36 @@ class Controller extends React.Component {
/>
{I18n.t('On')}
</div>
{this.props.matter.controller.enabled && this.props.alive ? <div>
<Button
variant="contained"
disabled={this.state.discoveryRunning}
startIcon={this.state.discoveryRunning ? <CircularProgress size={20} /> : <Search />}
onClick={() => {
this.setState({ discoveryRunning: true, discovered: [] }, () =>
this.props.socket.sendTo(`matter.${this.props.instance}`, 'controllerDiscovery', { })
.then(result => {
if (result.error) {
this.setState({ discoveryRunning: false });
window.alert(`Cannot discover: ${result.error}`);
} else {
this.setState({ discovered: result.result, discoveryDone: true, discoveryRunning: false });
}
}));
}}
>
{I18n.t('Discovery devices')}
</Button>
</div> : null}
</div>;
}
}

Controller.propTypes = {
matter: PropTypes.object,
updateConfig: PropTypes.func,
alive: PropTypes.bool,
registerMessageHandler: PropTypes.func,
};

export default withStyles(styles)(Controller);
14 changes: 13 additions & 1 deletion src-admin/src/Tabs/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
InputLabel,
MenuItem,
Select,
TextField,
TextField, FormControlLabel, Checkbox,
} from '@mui/material';

import { I18n, Logo } from '@iobroker/adapter-react-v5';
Expand Down Expand Up @@ -239,6 +239,18 @@ class Options extends React.Component {
{I18n.t('Sync credentials with %s', this.state.iotInstance.replace('system.adapter.', ''))}
</Button> : null}
</div>
<div style={{ marginTop: 50 }}>
<FormControlLabel
control={
<Checkbox
checked={this.props.native.debug}
onChange={e => this.props.onChange('debug', e.target.checked)}
color="primary"
/>
}
label={I18n.t('Show all debug logs')}
/>
</div>
<div style={{ marginTop: 50 }}>
<Button
disabled={!this.props.alive}
Expand Down
Loading

0 comments on commit c5b6330

Please sign in to comment.