Skip to content

Commit

Permalink
Backend is working
Browse files Browse the repository at this point in the history
GermanBluefox committed Dec 26, 2024
1 parent 9bfaa11 commit 4f2c34a
Showing 7 changed files with 436 additions and 24 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -12,6 +12,48 @@
**This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0.

## Description
This adapter uses cloud engine to detect and identify faces on the images. It can be used to identify the person on the image.

Before start, you need running account on https://iobroker.pro and subscription for Assistant or Remote connection.

After that the persons must be created and trained. This can be done in the settings of the adapter on the "persons" tab. Maximum 10 persons can be created.

## Usage
### Messages
As the persons are created and trained, you can send the images in base64 format to the adapter, and it will try to identify the person on the image.
It is better to send 2 or more images to detect liveness of the images.

```js
sendTo('face.0', 'verify', { images: ['data:image/jpeg;base64,...'] }, (res) => {
console.log('Detected person: ' + res.person);
});
```

Image can be encoded as:
- URL to the image, like `http://example.com/image.jpg`
- Base64 encoded image, like `data:image/jpeg;base64,...`
- ioBroker URL, like `iobstate://0_userdata.0.image/val` (`/val` is optional). State could be again URL to the image, Base64 encoded image or ioBroker URL. Up to 3 recursive levels are supported.
- ioBroker URL, like `iobobject://system.adapter.face.0/common.extIcon`

If person is detected, the state `face.X.persons.<PERSON>` will be set to true with no acknowledge.

### States
Following states are created:
- `face.X.persons.<PERSON>` - Every created person will have the state. If the person is detected, the state will be set to true.
- `face.X.images.upload` - Write to this state the image format described above to detect the person on the image.
- `face.X.images.uploaded` - Current number of uploaded images. We suggest to upload 2 or more images to detect the liveness. If the image is older than 15 seconds it will be removed from the queue.
- `face.X.images.verify` - Trigger the identification of the person on the uploaded images.
- `face.X.images.enroll` - Trigger the training of the person that is defined in this state on the uploaded images.
- `face.X.images.uploadResult` - The result of the detection or enrollment. If the person is detected, the state `face.X.persons.<PERSON>` will be set to true and the `face.X.images.uploadResult` to the person ID.

Following values could be in `face.X.images.uploadResult`:
- `PERSON_ID` - The person ID that was detected on the images.
- `<no person>` - No person was identified on the images.
- `<authentication error>` - Credentials are wrong or the subscription is expired.
- `<no images>` - No images were uploaded.
- `<cannot enroll>` - Cannot enroll the person. The person is not created or not trained.
- `<person not found>` - Person that should be enrolled is not found.
- `<ERROR_TEXT>` - Any other error.

<!--
Placeholder for the next version (at the beginning of the line):
81 changes: 78 additions & 3 deletions io-package.json
Original file line number Diff line number Diff line change
@@ -2,9 +2,7 @@
"common": {
"name": "face",
"version": "0.0.1",
"news": {

},
"news": {},
"title": "Face detection",
"titleLang": {
"en": "Face detection"
@@ -82,6 +80,83 @@
"name": "Persons"
},
"native": {}
},
{
"_id": "images",
"type": "folder",
"common": {
"name": "Upload images"
},
"native": {}
},
{
"_id": "images.upload",
"type": "state",
"common": {
"name": "Data URL image",
"type": "string",
"role": "state",
"write": true,
"read": true,
"def": ""
},
"native": {}
},
{
"_id": "images.uploaded",
"type": "state",
"common": {
"name": "Uploaded images",
"desc": "Number of uploaded images to perform verification or training",
"type": "number",
"role": "state",
"write": false,
"read": true,
"def": 0
},
"native": {}
},
{
"_id": "images.verify",
"type": "state",
"common": {
"name": "Verify",
"desc": "Trigger verification of uploaded images against known persons",
"type": "boolean",
"role": "button",
"write": true,
"read": false,
"def": false
},
"native": {}
},
{
"_id": "images.enroll",
"type": "state",
"common": {
"name": "Train person",
"desc": "Write person name to enroll",
"type": "string",
"role": "state",
"write": true,
"read": false,
"def": ""
},
"native": {}
},
{
"_id": "images.lastResult",
"type": "state",
"common": {
"name": "Last result",
"desc": "Last result of enroll or verify. Errors starts with '<' and ends with '>'",
"type": "string",
"role": "state",
"write": false,
"read": true,
"def": ""
},
"native": {}
}
]
}
20 changes: 11 additions & 9 deletions src-admin/src/Tabs/Persons.tsx
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ import { Check, Close, Delete, Person, Add, Edit, QuestionMark } from '@mui/icon

import { type AdminConnection, I18n, type ThemeType } from '@iobroker/adapter-react-v5';

import type { FaceAdapterConfig, PERSON_ID, TOKEN } from '../types';
import type { ENGINE, FaceAdapterConfig, PERSON_ID, TOKEN } from '../types';
import { Camera } from '../components/Camera';
import { Comm } from '../components/Comm';

@@ -117,7 +117,7 @@ class Persons extends Component<PersonsProps, PersonsState> {
};
}

renderResultsDialog() {
renderResultsDialog(): React.JSX.Element | null {
if (this.state.verifyResult === null || this.state.showVerifyDialog === null) {
return null;
}
@@ -197,19 +197,21 @@ class Persons extends Component<PersonsProps, PersonsState> {
disabled={!this.state.images.length || this.state.processing}
onClick={async () => {
if (await this.validateTokens()) {
const engine: ENGINE = this.props.native.engine || 'iobroker';
await this.setStateAsync({ processing: true });
try {
const isEnrolled = await Comm.enroll(
const result = await Comm.enroll(
this.state.accessToken,
this.props.native.engine || 'iobroker',
this.state.persons[this.state.showEnrollDialog as number].id,
engine,
this.state.images,
this.state.persons[this.state.showEnrollDialog as number].id,
);
if (isEnrolled) {
if (
result.enrolled &&
!this.state.persons[this.state.showEnrollDialog as number][engine]
) {
const persons = [...this.state.persons];
persons[this.state.showEnrollDialog as number][
this.props.native.engine || 'iobroker'
] = true;
persons[this.state.showEnrollDialog as number][engine] = true;
this.setState({ persons });
}
this.setState({ showEnrollDialog: null, processing: false });
9 changes: 7 additions & 2 deletions src-admin/src/components/Comm.ts
Original file line number Diff line number Diff line change
@@ -24,7 +24,12 @@ export class Comm {
return data.persons;
}

static async enroll(accessToken: TOKEN, engine: string, personId: PERSON_ID, images: string[]): Promise<number> {
static async enroll(
accessToken: TOKEN,
engine: string,
images: string[],
personId: PERSON_ID,
): Promise<{ enrolled: boolean }> {
const response = await fetch(`${URL}enroll/${personId}?engine=${engine || 'iobroker'}`, {
method: 'POST',
headers: {
@@ -33,7 +38,7 @@ export class Comm {
},
body: JSON.stringify(images),
});
return (await response.json()).advancedId;
return await response.json();
}

static async verify(
12 changes: 6 additions & 6 deletions src-admin/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export type FaceAdapterConfig = {
engine: 'iobroker' | 'advanced';
login: string;
password: string;
};

export type USER_EMAIL = string;
export type PERSON_ID = string;
export type ISO_TIME = string;
export type ENGINE = 'iobroker' | 'advanced';
export type TOKEN = string;

export type FaceAdapterConfig = {
engine: ENGINE;
login: string;
password: string;
};
37 changes: 36 additions & 1 deletion src/lib/comm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from 'axios';
import type { PERSON_ID, TOKEN } from '../../src-admin/src/types';
const URL = 'https://face.iobroker.in/';

export class Comm {
@@ -20,7 +21,12 @@ export class Comm {
return response.data.persons;
}

static async enroll(accessToken: string, engine: string, personId: string, images: string[]): Promise<number> {
static async enroll(
accessToken: string,
engine: string,
images: string[],
personId: string,
): Promise<{ enrolled: boolean }> {
const response = await axios.post(`${URL}enroll/${personId}?engine=${engine || 'iobroker'}`, images, {
headers: {
Authorization: `Bearer ${accessToken}`,
@@ -30,6 +36,35 @@ export class Comm {
return response.data.advancedId;
}

static async verify(
accessToken: TOKEN,
engine: string,
images: string[],
personId?: PERSON_ID,
): Promise<{ person: PERSON_ID; results: { person: PERSON_ID; result: boolean; error?: string }[] }> {
if (personId) {
const response = await fetch(`${URL}verify?person=${personId}&engine=${engine || 'iobroker'}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-type': 'application/json',
},
body: JSON.stringify(images),
});
return await response.json();
}

const response = await fetch(`${URL}verify?engine=${engine || 'iobroker'}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-type': 'application/json',
},
body: JSON.stringify(images),
});
return await response.json();
}

static async edit(accessToken: string, personId: string, data: { id: string; name: string }): Promise<void> {
await axios.patch(`${URL}person/${personId}`, data, {
headers: {
Loading

0 comments on commit 4f2c34a

Please sign in to comment.