Skip to content

Instantly share code, notes, and snippets.

@stutrek
Last active December 21, 2020 05:40
Show Gist options
  • Save stutrek/dc1666c068b4edaf4d3722d783b49c87 to your computer and use it in GitHub Desktop.
Save stutrek/dc1666c068b4edaf4d3722d783b49c87 to your computer and use it in GitHub Desktop.
import { Gpio } from 'pigpio';
import NanoTimer from 'nanotimer';
function sleep(ms: number) {
return new Promise((resolve) => {
new NanoTimer().setTimeout(resolve, '', `${ms * 1000}u`);
});
}
const stepperConfigs = {
'28BYJ48': {
stepsPerRotation: 4096,
resetAfterStep: true,
delayAfterStep: 1.2,
pattern: [
[0, 0, 0, 1],
[0, 0, 1, 1],
[0, 0, 1, 0],
[0, 1, 1, 0],
[0, 1, 0, 0],
[1, 1, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 1],
],
},
'28BYJ48-2': {
stepsPerRotation: 4096,
resetAfterStep: true,
delayAfterStep: 1.2,
pattern: [
[1, 1, 1, 0],
[1, 1, 0, 0],
[1, 1, 0, 1],
[1, 0, 0, 1],
[1, 0, 1, 1],
[0, 0, 1, 1],
[0, 1, 1, 1],
[0, 1, 1, 0],
],
},
} as const;
type StepperConfigs = typeof stepperConfigs;
type StepperConfig = StepperConfigs[keyof StepperConfigs];
type Easing = (ratioCompleted: number) => number;
const futureDate = (ms: number) => {
return new Date(new Date().valueOf() + ms);
};
class Task {
constructor(
public stepsTotal: number,
public direction: 1 | -1,
public finishTime: Date,
public easing: Easing = (x) => x
) {
this.startTime = new Date();
this.msTotal = finishTime.valueOf() - this.startTime.valueOf();
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
resolve: (value: PromiseLike<undefined> | undefined) => void = () => {};
reject: (reason: any) => void = () => {};
promise: Promise<undefined>;
isComplete = false;
msTotal: number;
startTime: Date;
stepsSoFar = 0;
claimNextSteps() {
const msElapsed = new Date().valueOf() - this.startTime.valueOf();
const ratioCompleted = Math.min(msElapsed / this.msTotal, 1);
const easedRatio = this.easing(ratioCompleted);
const stepsThatShouldHaveBeenCompleted = Math.floor(
this.stepsTotal * easedRatio
);
const stepsThisTime =
stepsThatShouldHaveBeenCompleted - this.stepsSoFar;
this.stepsSoFar = stepsThatShouldHaveBeenCompleted;
if (ratioCompleted === 1) {
this.isComplete = true;
}
return stepsThisTime;
}
returnSteps(count: number) {
this.stepsSoFar -= count;
}
}
export class Stepper {
constructor(pinNumbers: number[], stepperModel: keyof StepperConfigs) {
this.pins = pinNumbers.map(
(pinNumber) => new Gpio(pinNumber, { mode: 0 })
);
this.stepperConfig = stepperConfigs[stepperModel];
}
pins: Gpio[];
stepperConfig: StepperConfig;
task?: Task;
timer?: NanoTimer;
private currentStepPatternIndex = 0;
private start() {
if (this.timer) {
return;
}
this.timer = new NanoTimer();
const continuer = async () => {
await this.step();
if (this.task && this.task.isComplete === false) {
this.timer?.setTimeout(continuer, '', '50u');
} else {
this.timer = undefined;
this.turnOff();
}
};
this.timer.setTimeout(continuer, '', '50u');
}
private async step() {
if (!this.task) {
return undefined;
}
const stepsRequired = this.task.claimNextSteps();
if (stepsRequired === 0) {
return;
}
for (var i = 0; i < stepsRequired; i++) {
this.incrementStepPatternIndex();
const stepPattern = this.stepperConfig.pattern[
this.currentStepPatternIndex
];
if (this.stepperConfig.delayAfterStep) {
await sleep(this.stepperConfig.delayAfterStep);
}
if (this.stepperConfig.resetAfterStep) {
for (const pin of this.pins) {
pin.digitalWrite(0);
}
}
for (const [pinIndex, pin] of this.pins.entries()) {
if (stepPattern[pinIndex]) {
pin.digitalWrite(stepPattern[pinIndex]);
}
}
}
if (this.task.isComplete) {
this.task.resolve(undefined);
this.turnOff();
}
}
private turnOff() {
this.task?.reject(undefined);
this.task = undefined;
for (const pin of this.pins) {
pin.digitalWrite(1);
}
}
private incrementStepPatternIndex() {
if (!this.task) {
throw new Error('Incrementing step pattern requires a task');
}
this.currentStepPatternIndex =
this.currentStepPatternIndex + this.task.direction;
if (this.currentStepPatternIndex <= -1) {
this.currentStepPatternIndex += this.stepperConfig.pattern.length;
}
if (this.currentStepPatternIndex >= this.stepperConfig.pattern.length) {
this.currentStepPatternIndex = 0;
}
}
turn({
rotations = 1,
direction = 1 as 1 | -1,
duration = 5,
easing = (x: number) => x,
}) {
this.task?.reject(undefined);
const steps = rotations * this.stepperConfig.stepsPerRotation;
const finishTime = futureDate(duration * 1000);
this.task = new Task(steps, direction, finishTime, easing);
this.start();
return this.task.promise;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment