// Code in this file is in parts based on: // https://github.com/adafruit/Adafruit_Python_PCA9685/blob/master/Adafruit_PCA9685/PCA9685.py // Copyright (c) 2016 Adafruit Industries // Author: Tony DiCola // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // https://github.com/tessel/servo-pca9685/blob/master/index.js // Copyright 2014 Technical Machine, Inc. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. import type { ImplementationConfigBase } from '../lib/adapter-config'; import { Delay } from '../lib/async'; import { getAllAddresses } from '../lib/i2c'; import type { DeviceHandlerInfo } from './device-handler-base'; import { DeviceHandlerBase } from './device-handler-base'; export interface PCA9685Config extends ImplementationConfigBase { frequency: number; } enum Register { MODE1 = 0x00, MODE2 = 0x01, SUBADR1 = 0x02, SUBADR2 = 0x03, SUBADR3 = 0x04, PRESCALE = 0xfe, LED0_ON_L = 0x06, LED0_ON_H = 0x07, LED0_OFF_L = 0x08, LED0_OFF_H = 0x09, ALL_LED_ON_L = 0xfa, ALL_LED_ON_H = 0xfb, ALL_LED_OFF_L = 0xfc, ALL_LED_OFF_H = 0xfd, } enum Mode { RESTART = 0x80, SLEEP = 0x10, ALLCALL = 0x01, // INVERT = 0x10, OUTDRV = 0x04, EXTCLK = 0x40, } export class PCA9685Handler extends DeviceHandlerBase { private currentDelay?: Delay; async startAsync(): Promise { this.debug('Starting'); await this.adapter.extendObject(this.hexAddress, { type: 'device', common: { name: `${this.hexAddress} (${this.name})`, role: 'value', }, native: this.deviceConfig, }); for (let i = 0; i < 16; i++) { let value = this.getStateValue(i); if (value === undefined) { value = 0; await this.setStateAckAsync(i, value); } await this.adapter.extendObject(`${this.hexAddress}.${i}`, { type: 'state', common: { name: `${this.hexAddress} Channel ${i}`, read: true, write: true, type: 'number', role: 'level.dimmer', min: 0, max: 4095, }, }); } await this.initializeDeviceAsync(); for (let i = 0; i < 16; i++) { this.addOutputListener(i); } } async stopAsync(): Promise { this.debug('Stopping'); if (this.currentDelay) { this.currentDelay.cancel(); } return Promise.resolve(); } private async initializeDeviceAsync(): Promise { this.debug('Initializing PCA9685'); await this.restartDeviceAsync(); await this.setPwmFrequencyAsync(this.config.frequency); for (let i = 0; i < 16; i++) { await this.setPwmValueAsync(i, this.getStateValue(i) || 0); } } private async restartDeviceAsync(): Promise { await this.writeByte(Register.MODE2, Mode.OUTDRV); await this.writeByte(Register.MODE1, Mode.ALLCALL); await this.delay(2); let mode1 = await this.readByte(Register.MODE1); mode1 = mode1 & ~Mode.SLEEP; await this.writeByte(Register.MODE1, mode1); await this.delay(2); } private async setPwmFrequencyAsync(frequencyHz: number): Promise { if (isNaN(frequencyHz)) { this.error(`Cannot set PWM frequency to ${frequencyHz}`); return; } if (frequencyHz > 1526) { frequencyHz = 1526; } else if (frequencyHz < 24) { frequencyHz = 24; } let prescaleValue = 25000000.0; // 25MHz prescaleValue /= 4096.0; // 12-bit prescaleValue /= frequencyHz; prescaleValue -= 1.0; this.debug(`Setting PWM frequency to ${frequencyHz} Hz`); this.debug(`Estimated pre-scale: ${prescaleValue} Hz`); const prescale = Math.floor(prescaleValue + 0.5); this.debug(`Final pre-scale: ${prescale}`); const oldMode = await this.readByte(Register.MODE1); const newMode = (oldMode & 0x7f) | Mode.SLEEP; this.debug(`Old mode: ${oldMode}`); this.debug(`New mode: ${newMode}`); await this.writeByte(Register.MODE1, newMode); // go to sleep await this.delay(2); await this.writeByte(Register.PRESCALE, prescale); await this.writeByte(Register.MODE1, oldMode); await this.delay(2); await this.writeByte(Register.MODE1, oldMode | 0x80); await this.delay(2); } private async setPwmValueAsync(channel: number, pwmValue: number): Promise { this.debug(`Received new PWM value ${pwmValue} for channel ${channel}`); if (isNaN(channel) || isNaN(pwmValue)) { this.error(`Cannot set PWM value ${pwmValue} for channel ${channel}`); return; } if (channel < 0) { channel = 0; } else if (channel > 15) { channel = 15; } let ledOn = 0; let ledOff = pwmValue; if (pwmValue >= 4095) { ledOn = 4096; ledOff = 0; pwmValue = 4095; } else if (pwmValue == undefined || pwmValue <= 0) { ledOn = 0; ledOff = 4096; pwmValue = 0; } try { if (await this.deviceNotInitializedAsync()) { await this.initializeDeviceAsync(); } await this.writeByte(Register.LED0_ON_L + 4 * channel, ledOn & 0xff); await this.writeByte(Register.LED0_ON_H + 4 * channel, ledOn >> 8); await this.writeByte(Register.LED0_OFF_L + 4 * channel, ledOff & 0xff); await this.writeByte(Register.LED0_OFF_H + 4 * channel, ledOff >> 8); } catch (e: any) { this.error(`Couldn't send current PWM value: ${e}`); } this.debug(`Writing values for channel ${channel}: on=${ledOn} | off=${ledOff} (PWM ${pwmValue})`); await this.setStateAckAsync(channel, pwmValue); } private async deviceNotInitializedAsync(): Promise { return (await this.readByte(Register.MODE1)) != Mode.ALLCALL; } private addOutputListener(channel: number): void { this.adapter.addStateChangeListener( `${this.hexAddress}.${channel}`, async (_oldValue: number, newValue: number) => await this.setPwmValueAsync(channel, newValue), ); } private async delay(milliseconds: number): Promise { const delay = new Delay(milliseconds); this.currentDelay = delay; await delay.runAsnyc(); } } export const PCA9685: DeviceHandlerInfo = { type: 'PCA9685', createHandler: (deviceConfig, adapter) => new PCA9685Handler(deviceConfig, adapter), names: [{ name: 'PCA9685', addresses: getAllAddresses(0x40, 64) }], config: { 'PCA9685.frequency': { type: 'number', default: 100, min: 24, max: 1526, step: 1, label: 'PWM Frequency', xs: 7, sm: 5, md: 3, unit: 'Hz', }, }, };