Skip to content

Commit

Permalink
Queue up multiple requested polls
Browse files Browse the repository at this point in the history
... instead of rejecting them if an other poll is currently active
Added method `isPolling()` to get the current polling state

May fix #59
  • Loading branch information
crycode-de committed Dec 19, 2023
1 parent 98164bf commit 05c2e0c
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 9 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Changelog

## 4.0.0 WIP

- Queue up multiple requested polls instead of rejecting them if an other poll is currently active
💥 This may be a breaking change if you rely on the rejection to prevent multiple polls
- Added method `isPolling()` to get the current polling state

## v3.1.0 2023-04-12

- Fix issues if enableInterrupt() invoked multiple times with same or different gpio pin number. (thanks to Lyndel McGee [#55](https://github.com/crycode-de/node-pcf8574/issues/55), [#56](https://github.com/crycode-de/node-pcf8574/pull/56))
- Fix issues if enableInterrupt() invoked multiple times with same or different gpio pin number. (thanks to Lyndel McGee [#55](https://github.com/crycode-de/node-pcf8574/issues/55), [#56](https://github.com/crycode-de/node-pcf8574/pull/56))
💥 This may be a breaking change if you called `enabledInterrupt()` multiple times without calling `disableInterrupt()` first - wich was never intended!

## v3.0.1 2023-03-21
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pcf8574",
"version": "3.1.0",
"version": "4.0.0",
"description": "Control each pin of a PCF8574/PCF8574A/PCF8575 I2C port expander IC.",
"keywords": [
"pcf8574",
Expand Down
22 changes: 17 additions & 5 deletions src/pcf857x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { I2CBus } from 'i2c-bus';
import { Gpio } from 'onoff';
import { PCF8574 } from './pcf8574';
import { PCF8575 } from './pcf8575';
import { PromiseQueue } from './promise-queue';

/**
* Enum of the known IC types.
Expand Down Expand Up @@ -123,6 +124,9 @@ export abstract class PCF857x<PinNumber extends PCF8574.PinNumber | PCF8575.PinN
/** Flag if we are currently polling changes from the PCF857x IC. */
private _currentlyPolling: boolean = false;

/** PromiseQueue to handle requested polls in order. */
private _pollQueue: PromiseQueue = new PromiseQueue();

/** Pin number of GPIO to detect interrupts, or null by default. */
private _gpioPin: number | null = null;

Expand Down Expand Up @@ -223,8 +227,8 @@ export abstract class PCF857x<PinNumber extends PCF8574.PinNumber | PCF8575.PinN
* Internal function to handle a GPIO interrupt.
*/
private _handleInterrupt (): void {
// poll the current state and ignore any rejected promise
this._poll().catch(() => { /* nothing to do here */ });
// enqueue a poll of current state and ignore any rejected promise
this._pollQueue.enqueue(() => this._poll()).catch(() => { /* nothing to do here */ });
}

/**
Expand Down Expand Up @@ -310,11 +314,11 @@ export abstract class PCF857x<PinNumber extends PCF8574.PinNumber | PCF8575.PinN
* Manually poll changed inputs from the PCF857x IC.
* If a change on an input is detected, an "input" Event will be emitted with a data object containing the "pin" and the new "value".
* This have to be called frequently enough if you don't use a GPIO for interrupt detection.
* If you poll again before the last poll was completed, the promise will be rejected with an error.
* If you poll again before the last poll was completed, the new poll will be queued up the be executed after the current poll.
* @return {Promise}
*/
public doPoll (): Promise<void> {
return this._poll();
return this._pollQueue.enqueue(() => this._poll());
}

/**
Expand Down Expand Up @@ -391,6 +395,14 @@ export abstract class PCF857x<PinNumber extends PCF8574.PinNumber | PCF8575.PinN
});
}

/**
* Returns if one or multiple polls are currently queued for execution.
* @returns `true` if we are currently polling.
*/
public isPolling (): boolean {
return !this._pollQueue.isEmpty();
}

/**
* Define a pin as an output.
* This marks the pin to be used as an output pin.
Expand Down Expand Up @@ -440,7 +452,7 @@ export abstract class PCF857x<PinNumber extends PCF8574.PinNumber | PCF8575.PinN
return this._setNewState()
// ... and then poll all current inputs with noEmit on this pin to suppress the event
.then(() => {
return this._poll(pin);
return this._pollQueue.enqueue(() => this._poll(pin));
});
}

Expand Down
90 changes: 90 additions & 0 deletions src/promise-queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Interface to describe a queued promise in a `PromiseQueue`.
*/
interface QueuedPromise<T = any> {
promise: () => Promise<T>;
resolve: (value: T) => void;
reject: (reason?: any) => void;
}

/**
* A simple Promise Queue to allow the execution of some tasks in the correct order.
*
* (c) Peter Müller <[email protected]>
*/
export class PromiseQueue {

/**
* Queued Promises.
*/
private queue: QueuedPromise[] = [];

/**
* Indicator that we are working on a Promise.
*/
private working: boolean = false;

/**
* Enqueue a Promise.
* This adds the given Promise to the queue. If the queue was empty the Promise
* will be started immediately.
* @param promise Function which returns the Promise.
* @returns A Promise which will be resolves (or rejected) if the queued promise is done.
*/
public enqueue<T = void> (promise: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push({
promise,
resolve,
reject,
});
this.dequeue();
});
}

/**
* Returns if the queue is empty and no more Promises are queued.
* @returns `true` if a Promise is active.
*/
public isEmpty(): boolean {
return !this.working && this.queue.length == 0;
}

/**
* Get the first Promise of the queue and start it if there is no other
* Promise currently active.
* @returns `true` if Promise from the queue is started, `false` there is already an other active Promise or the queue is empty.
*/
private dequeue (): boolean {
if (this.working) {
return false;
}

const item = this.queue.shift();
if (!item) {
return false;
}

try {
this.working = true;
item.promise()
.then((value) => {
item.resolve(value);
})
.catch((err) => {
item.reject(err);
})
.finally(() => {
this.working = false;
this.dequeue()
});

} catch (err) {
item.reject(err);
this.working = false;
this.dequeue();
}

return true;
}
}

0 comments on commit 05c2e0c

Please sign in to comment.