diff --git a/docs/integrations/grid-eye.md b/docs/integrations/grid-eye.md
index 71389fb..7fa8cc5 100644
--- a/docs/integrations/grid-eye.md
+++ b/docs/integrations/grid-eye.md
@@ -77,6 +77,7 @@ When placing your sensor you need to consider a few factors to get reliable resu
| `busNumber` | Number | `1` | I2C bus number of your machine that the sensor is connected to. |
| `address` | Number | `0x69` | I2C address of the D6T sensor that you want to use. |
| `deltaThreshold` | Number | `2` | Minimum temperature difference between average and single temperature pixel in °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
diff --git a/src/config/config.service.spec.fail.yml b/src/config/config.service.spec.fail.yml
index 251d21f..aec5c89 100644
--- a/src/config/config.service.spec.fail.yml
+++ b/src/config/config.service.spec.fail.yml
@@ -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
diff --git a/src/config/config.service.spec.pass.yml b/src/config/config.service.spec.pass.yml
index 5dc8992..4fccf1c 100644
--- a/src/config/config.service.spec.pass.yml
+++ b/src/config/config.service.spec.pass.yml
@@ -133,6 +133,7 @@ gridEye:
busNumber: 1
address: 0x69
deltaThreshold: 2
+ maskZeroBasedValues: true
heatmap:
enabled: true
minTemperature: 23
diff --git a/src/config/config.service.spec.ts b/src/config/config.service.spec.ts
index 7245463..cf5302f 100644
--- a/src/config/config.service.spec.ts
+++ b/src/config/config.service.spec.ts
@@ -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`,
diff --git a/src/integrations/grid-eye/grid-eye.config.ts b/src/integrations/grid-eye/grid-eye.config.ts
index 3b7b342..0475ff5 100644
--- a/src/integrations/grid-eye/grid-eye.config.ts
+++ b/src/integrations/grid-eye/grid-eye.config.ts
@@ -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();
}
diff --git a/src/integrations/grid-eye/grid-eye.service.spec.ts b/src/integrations/grid-eye/grid-eye.service.spec.ts
index 0a02333..636dad2 100644
--- a/src/integrations/grid-eye/grid-eye.service.spec.ts
+++ b/src/integrations/grid-eye/grid-eye.service.spec.ts
@@ -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';
@@ -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';
@@ -31,10 +34,28 @@ describe('GridEyeService', () => {
add: jest.fn(),
};
const clusterService = jest.fn();
+ let mockConfig: Partial;
+ 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],
@@ -43,6 +64,8 @@ describe('GridEyeService', () => {
.useValue(entitiesService)
.overrideProvider(ClusterService)
.useValue(clusterService)
+ .overrideProvider(ConfigService)
+ .useValue(configService)
.compile();
service = module.get(GridEyeService);
@@ -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')
diff --git a/src/integrations/grid-eye/grid-eye.service.ts b/src/integrations/grid-eye/grid-eye.service.ts
index aa7976d..9ebb5ae 100644
--- a/src/integrations/grid-eye/grid-eye.service.ts
+++ b/src/integrations/grid-eye/grid-eye.service.ts
@@ -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;
@@ -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 {
+ 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[][];
}
/**