Skip to content

Commit

Permalink
feat(grid-eye): add option to mask zero based values (#891)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alfiegerner committed Sep 21, 2021
1 parent 9fc0eec commit c8f5b95
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/integrations/grid-eye.md
Expand Up @@ -77,6 +77,7 @@ When placing your sensor you need to consider a few factors to get reliable resu
| `busNumber` | Number | `1` | I<sup>2</sup>C bus number of your machine that the sensor is connected to. |
| `address` | Number | `0x69` | I<sup>2</sup>C address of the D6T sensor that you want to use. |
| `deltaThreshold` | Number | `2` | Minimum temperature difference between average and single temperature pixel in &deg;C for it to be considered as human presence. Increase if you are seeing false positives, decrease if you are seeing false negatives. |
| `maskZeroBasedValues` | Boolean | `false` | Mask values < 1 that are incorrectly reported, replacing with nearest valid value or mean of grid for first pixel. |
| `heatmap` | [Heatmap](#heatmap) | | A number of options for configuring the heatmap output. |

### Heatmap
Expand Down
1 change: 1 addition & 0 deletions src/config/config.service.spec.fail.yml
Expand Up @@ -142,6 +142,7 @@ gridEye:
busNumber: 1
address: 0x69
deltaThreshold: 2
maskZeroBasedValues: 10 # TYPE ERROR: Boolean required
heatmap:
enabled: true
minTemperature: "23" # TYPE ERROR: Number Required
Expand Down
1 change: 1 addition & 0 deletions src/config/config.service.spec.pass.yml
Expand Up @@ -133,6 +133,7 @@ gridEye:
busNumber: 1
address: 0x69
deltaThreshold: 2
maskZeroBasedValues: true
heatmap:
enabled: true
minTemperature: 23
Expand Down
1 change: 1 addition & 0 deletions src/config/config.service.spec.ts
Expand Up @@ -93,6 +93,7 @@ describe('ConfigService', () => {
`bluetoothClassic.timeoutCycles`,
`bluetoothClassic.entityOverrides.ebef1234567890-55555-333.id`,
`omronD6t.heatmap.enabled`,
`gridEye.maskZeroBasedValues`,
`gridEye.heatmap.minTemperature`,
`gridEye.heatmap.rotation`,
`gpio.binarySensors[1].deviceClass`,
Expand Down
2 changes: 2 additions & 0 deletions src/integrations/grid-eye/grid-eye.config.ts
Expand Up @@ -8,6 +8,8 @@ export class GridEyeConfig {
address = 0x69;
@(jf.number().min(0).required())
deltaThreshold = 2;
@(jf.boolean().required())
maskZeroBasedValues = false;
@(jf.object({ objectClass: HeatmapOptions }).required())
heatmap = new HeatmapOptions();
}
53 changes: 52 additions & 1 deletion src/integrations/grid-eye/grid-eye.service.spec.ts
Expand Up @@ -4,6 +4,8 @@ const mockI2cBus = {
close: jest.fn(),
};

import { ConfigService } from '../../config/config.service';
import c from 'config';
import { Test, TestingModule } from '@nestjs/testing';
import { GridEyeService } from './grid-eye.service';
import { EntitiesModule } from '../../entities/entities.module';
Expand All @@ -12,6 +14,7 @@ import { ScheduleModule } from '@nestjs/schedule';
import { EntitiesService } from '../../entities/entities.service';
import { ClusterService } from '../../cluster/cluster.service';
import { Sensor } from '../../entities/sensor';
import { GridEyeConfig } from './grid-eye.config';
import i2cBus from 'i2c-bus';
import * as math from 'mathjs';

Expand All @@ -31,10 +34,28 @@ describe('GridEyeService', () => {
add: jest.fn(),
};
const clusterService = jest.fn();
let mockConfig: Partial<GridEyeConfig>;
const configService = {
get: jest.fn().mockImplementation((key: string) => {
return key === 'gridEye' ? mockConfig : c.get(key);
}),
};

beforeEach(async () => {
jest.clearAllMocks();

mockConfig = {
busNumber: 1,
address: 0x69,
deltaThreshold: 2,
maskZeroBasedValues: false,
heatmap: {
enabled: true,
minTemperature: 16,
maxTemperature: 40,
rotation: 0,
drawTemperatures: true,
},
};
const module: TestingModule = await Test.createTestingModule({
imports: [EntitiesModule, ConfigModule, ScheduleModule.forRoot()],
providers: [GridEyeService],
Expand All @@ -43,6 +64,8 @@ describe('GridEyeService', () => {
.useValue(entitiesService)
.overrideProvider(ClusterService)
.useValue(clusterService)
.overrideProvider(ConfigService)
.useValue(configService)
.compile();

service = module.get<GridEyeService>(GridEyeService);
Expand Down Expand Up @@ -184,6 +207,34 @@ describe('GridEyeService', () => {
expect(temperatures).toStrictEqual(math.ones([8, 8]));
});

it('should override zero based temperatures when configured', async () => {
mockConfig.maskZeroBasedValues = true;
const temperatureSpy = jest
.spyOn(service, 'getPixelTemperature')
.mockResolvedValue(16)
.mockResolvedValueOnce(0)
.mockResolvedValueOnce(0.3)
.mockResolvedValueOnce(0.5);

const temperatures = await service.getPixelTemperatures();
expect(temperatureSpy).toHaveBeenCalledTimes(64);
expect(temperatures.flat().every((x) => x >= 1)).toBe(true);
});

it('should not override zero based temperatures when not configured', async () => {
mockConfig.maskZeroBasedValues = false;
const temperatureSpy = jest
.spyOn(service, 'getPixelTemperature')
.mockResolvedValue(16)
.mockResolvedValueOnce(0)
.mockResolvedValueOnce(0.3)
.mockResolvedValueOnce(0.5);

const temperatures = await service.getPixelTemperatures();
expect(temperatureSpy).toHaveBeenCalledTimes(64);
expect(temperatures.flat().every((x) => x >= 1)).toBe(false);
});

it('should get the temperature of a single pixel', async () => {
const registerSpy = jest
.spyOn(service, 'getRegister')
Expand Down
22 changes: 20 additions & 2 deletions src/integrations/grid-eye/grid-eye.service.ts
Expand Up @@ -24,7 +24,8 @@ const FRAMERATE_REGISTER = 0x02;
@Injectable()
export class GridEyeService
extends ThermopileOccupancyService
implements OnApplicationBootstrap, OnApplicationShutdown {
implements OnApplicationBootstrap, OnApplicationShutdown
{
private readonly config: GridEyeConfig;
private readonly logger: Logger;
private i2cBus: PromisifiedBus;
Expand Down Expand Up @@ -105,7 +106,24 @@ export class GridEyeService
temperatures.push(await this.getPixelTemperature(i));
}

return math.reshape(temperatures, [8, 8]) as number[][];
let grid = math.reshape(temperatures, [8, 8]) as number[][];
if (this.config.maskZeroBasedValues) {
grid = await this.maskZeroBasedValues(grid);
}
return grid;
}

/**
* Replace 0 based values (0 to 1) with nearest preceding valid value.
*
* @returns 8x8 matrix of temperatures
*/
async maskZeroBasedValues(temperatures: number[][]): Promise<number[][]> {
const correctedMean = math.mean(temperatures.flat().filter(v => v >= 1 || v < 0))

return math.matrix(temperatures)
.map(v => v >= 1 || v < 0 ? v : correctedMean)
.toArray() as number[][];
}

/**
Expand Down

0 comments on commit c8f5b95

Please sign in to comment.