diff --git a/AMBX.cpp b/AMBX.cpp new file mode 100644 index 00000000..4df787d3 --- /dev/null +++ b/AMBX.cpp @@ -0,0 +1,253 @@ +/* Copyright (C) 2021 Aran Vink. All rights reserved. + + This software may be distributed and modified under the terms of the GNU + General Public License version 2 (GPL2) as published by the Free Software + Foundation and appearing in the file GPL2.TXT included in the packaging of + this file. Please note that GPL2 Section 2[b] requires that all works based + on this software must also be made publicly available under the terms of + the GPL2 ("Copyleft"). + + Contact information + ------------------- + + Aran Vink + e-mail : aranvink@gmail.com + */ + +#include "AMBX.h" +// To enable serial debugging see "settings.h" +//#define EXTRADEBUG // Uncomment to get even more debugging data + +AMBX::AMBX(USB *p) : +pUsb(p), // pointer to USB class instance - mandatory +bAddress(0) // device address - mandatory +{ + for(uint8_t i = 0; i < AMBX_MAX_ENDPOINTS; i++) { + epInfo[i].epAddr = 0; + epInfo[i].maxPktSize = (i) ? 0 : 8; + epInfo[i].bmSndToggle = 0; + epInfo[i].bmRcvToggle = 0; + epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER; + } + + if(pUsb) // register in USB subsystem + pUsb->RegisterDeviceClass(this); //set devConfig[] entry +} + +uint8_t AMBX::Init(uint8_t parent, uint8_t port, bool lowspeed) { + uint8_t buf[sizeof (USB_DEVICE_DESCRIPTOR)]; + USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast(buf); + uint8_t rcode; + UsbDevice *p = NULL; + EpInfo *oldep_ptr = NULL; + uint16_t PID; + uint16_t VID; + + // get memory address of USB device address pool + AddressPool &addrPool = pUsb->GetAddressPool(); +#ifdef EXTRADEBUG + Notify(PSTR("\r\nAMBX Init"), 0x80); +#endif + // check if address has already been assigned to an instance + if(bAddress) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nAddress in use"), 0x80); +#endif + return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE; + } + + // Get pointer to pseudo device with address 0 assigned + p = addrPool.GetUsbDevicePtr(0); + + if(!p) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nAddress not found"), 0x80); +#endif + return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL; + } + + if(!p->epinfo) { +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nepinfo is null"), 0x80); +#endif + return USB_ERROR_EPINFO_IS_NULL; + } + + // Save old pointer to EP_RECORD of address 0 + oldep_ptr = p->epinfo; + + // Temporary assign new pointer to epInfo to p->epinfo in order to avoid toggle inconsistence + p->epinfo = epInfo; + + p->lowspeed = lowspeed; + + // Get device descriptor + rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)buf); // Get device descriptor - addr, ep, nbytes, data + // Restore p->epinfo + p->epinfo = oldep_ptr; + + if(rcode) + goto FailGetDevDescr; + + VID = udd->idVendor; + PID = udd->idProduct; + + if(VID != AMBX_VID || (PID != AMBX_PID)) + goto FailUnknownDevice; + + // Allocate new address according to device class + bAddress = addrPool.AllocAddress(parent, false, port); + + if(!bAddress) + return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL; + + // Extract Max Packet Size from device descriptor + epInfo[0].maxPktSize = udd->bMaxPacketSize0; + + // Assign new address to the device + rcode = pUsb->setAddr(0, 0, bAddress); + if(rcode) { + p->lowspeed = false; + addrPool.FreeAddress(bAddress); + bAddress = 0; +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nsetAddr: "), 0x80); + D_PrintHex (rcode, 0x80); +#endif + return rcode; + } +#ifdef EXTRADEBUG + Notify(PSTR("\r\nAddr: "), 0x80); + D_PrintHex (bAddress, 0x80); +#endif + + p->lowspeed = false; + + //get pointer to assigned address record + p = addrPool.GetUsbDevicePtr(bAddress); + if(!p) + return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL; + + p->lowspeed = lowspeed; + + // Assign epInfo to epinfo pointer - only EP0 is known + rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo); + if(rcode) + goto FailSetDevTblEntry; + + + /* The application will work in reduced host mode, so we can save program and data + memory space. After verifying the PID and VID we will use known values for the + configuration values for device, interface, endpoints for the AMBX Controller */ + + /* Initialize data structures for endpoints of device */ + epInfo[ AMBX_OUTPUT_PIPE ].epAddr = AMBX_ENDPOINT_OUT; // AMBX output endpoint + epInfo[ AMBX_OUTPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT; + epInfo[ AMBX_OUTPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints + epInfo[ AMBX_OUTPUT_PIPE ].maxPktSize = AMBX_EP_MAXPKTSIZE; + epInfo[ AMBX_OUTPUT_PIPE ].bmSndToggle = 0; + epInfo[ AMBX_OUTPUT_PIPE ].bmRcvToggle = 0; + + rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo); + if(rcode) + goto FailSetDevTblEntry; + + delay(200); //Give time for address change + + //For some reason this is need to make it work + rcode = pUsb->setConf(bAddress, epInfo[ AMBX_CONTROL_PIPE ].epAddr, 1); + if(rcode) + goto FailSetConfDescr; + + if(PID == AMBX_PID || PID) { + AMBXConnected = true; + } + onInit(); + + Notify(PSTR("\r\n"), 0x80); + return 0; // Successful configuration + + /* Diagnostic messages */ +FailGetDevDescr: +#ifdef DEBUG_USB_HOST + NotifyFailGetDevDescr(); + goto Fail; +#endif + +FailSetDevTblEntry: +#ifdef DEBUG_USB_HOST + NotifyFailSetDevTblEntry(); + goto Fail; +#endif + +FailSetConfDescr: +#ifdef DEBUG_USB_HOST + NotifyFailSetConfDescr(); +#endif + goto Fail; + +FailUnknownDevice: +#ifdef DEBUG_USB_HOST + NotifyFailUnknownDevice(VID, PID); +#endif + rcode = USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED; + +Fail: +#ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nAMBX Init Failed, error code: "), 0x80); + NotifyFail(rcode); +#endif + Release(); + return rcode; +} + +/* Performs a cleanup after failed Init() attempt */ +uint8_t AMBX::Release() { + AMBXConnected = false; + pUsb->GetAddressPool().FreeAddress(bAddress); + bAddress = 0; + return 0; +} + +uint8_t AMBX::Poll() { + return 0; +} + +void AMBX::Light_Command(uint8_t *data, uint16_t nbytes) { + #ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nLight command "), 0x80); + #endif + pUsb->outTransfer(bAddress, epInfo[ AMBX_OUTPUT_PIPE ].epAddr, nbytes, data); + //Do really short delay, I've noticed otherwise the controller will receive all command at once, and might not process all of them. + delay(1); +} + +void AMBX::setLight(uint8_t ambx_light, uint8_t r, uint8_t g, uint8_t b) { + writeBuf[0] = AMBX_PREFIX_COMMAND; + writeBuf[1] = ambx_light; + writeBuf[2] = AMBX_SET_COLOR_COMMAND; + writeBuf[3] = r; + writeBuf[4] = g; + writeBuf[5] = b; + Light_Command(writeBuf, AMBX_LIGHT_COMMAND_BUFFER_SIZE); +} + +void AMBX::setLight(AmbxLightsEnum ambx_light, AmbxColorsEnum color) { // Use this to set the Light with Color using the predefined in "AMBXEnums.h" + setLight(ambx_light, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color)); +} + +void AMBX::setAllLights(AmbxColorsEnum color) { // Use this to set the Color using the predefined colors in "AMBXEnums.h" + setLight(Sidelight_left, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color)); + setLight(Sidelight_right, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color)); + setLight(Wallwasher_center, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color)); + setLight(Wallwasher_left, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color)); + setLight(Wallwasher_right, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color)); +} + +void AMBX::onInit() { + #ifdef DEBUG_USB_HOST + Notify(PSTR("\r\nOnInit execute "), 0x80); + #endif + if(pFuncOnInit) + pFuncOnInit(); // Call the user function +} diff --git a/AMBX.h b/AMBX.h new file mode 100644 index 00000000..aed3a6fc --- /dev/null +++ b/AMBX.h @@ -0,0 +1,159 @@ +/* Copyright (C) 2021 Aran Vink. All rights reserved. + + This software may be distributed and modified under the terms of the GNU + General Public License version 2 (GPL2) as published by the Free Software + Foundation and appearing in the file GPL2.TXT included in the packaging of + this file. Please note that GPL2 Section 2[b] requires that all works based + on this software must also be made publicly available under the terms of + the GPL2 ("Copyleft"). + + Contact information + ------------------- + + Aran Vink + e-mail : aranvink@gmail.com + */ + +#ifndef _ambxusb_h_ +#define _ambxusb_h_ + +#include "Usb.h" +#include "AMBXEnums.h" + +/* AMBX data taken from descriptors */ +#define AMBX_EP_MAXPKTSIZE 40 // max size for data via USB + +/* Names we give to the 3 AMBX but note only one is actually used (output) */ +#define AMBX_CONTROL_PIPE 0 +#define AMBX_OUTPUT_PIPE 1 +#define AMBX_INPUT_PIPE 2 + +/* PID and VID of the different devices */ +#define AMBX_VID 0x0471 // Philips +#define AMBX_PID 0x083F // AMBX Controller + +/* Endpoint addresses */ +#define AMBX_ENDPOINT_IN 0x81 +#define AMBX_ENDPOINT_OUT 0x02 +#define AMBX_ENDPOINT_PNP 0x83 + +/* Output payload constants */ +#define AMBX_PREFIX_COMMAND 0xA1 +#define AMBX_SET_COLOR_COMMAND 0x03 + +/* LEFT/RIGHT lights. Normally placed adjecent to your screen. */ +#define AMBX_LIGHT_LEFT 0x0B +#define AMBX_LIGHT_RIGHT 0x1B + +/* Wallwasher lights. Normally placed behind your screen. */ +#define AMBX_LIGHT_WW_LEFT 0x2B +#define AMBX_LIGHT_WW_CENTER 0x3B +#define AMBX_LIGHT_WW_RIGHT 0x4B + +#define AMBX_LIGHT_COMMAND_BUFFER_SIZE 6 + + +#define AMBX_MAX_ENDPOINTS 3 + +/** + * This class implements support for AMBX + * One can only set the color of the bulbs, no other accesories like rumble pad, fans, etc. are supported + * + */ +class AMBX : public USBDeviceConfig { +public: + /** + * Constructor for the AMBX class. + * @param pUsb Pointer to USB class instance. + */ + AMBX(USB *pUsb); + + /** @name USBDeviceConfig implementation */ + /** + * Initialize the AMBX Controller. + * @param parent Hub number. + * @param port Port number on the hub. + * @param lowspeed Speed of the device. + * @return 0 on success. + */ + uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed); + /** + * Release the USB device. + * @return 0 on success. + */ + uint8_t Release(); + /** + * Poll the USB Input endpoins and run the state machines. + * @return 0 on success. + */ + uint8_t Poll(); + + /** + * Get the device address. + * @return The device address. + */ + virtual uint8_t GetAddress() { + return bAddress; + }; + + /** + * Used by the USB core to check what this driver support. + * @param vid The device's VID. + * @param pid The device's PID. + * @return Returns true if the device's VID and PID matches this driver. + */ + virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) { + return (vid == AMBX_VID && (pid == AMBX_PID)); + }; + /**@}*/ + + /** + * Use this to set the Color using RGB values. + * @param r,g,b RGB value. + */ + void setLight(uint8_t ambx_light, uint8_t r, uint8_t g, uint8_t b); + /** + * Use this to set the color using the predefined colors in ::ColorsEnum. + * @param color The desired color. + */ + void setLight(AmbxLightsEnum ambx_light, AmbxColorsEnum color); + + /** + * Use this to set the color using the predefined colors in ::ColorsEnum. + * @param color The desired color. + */ + void setAllLights(AmbxColorsEnum color); + + /** + * Used to call your own function when the controller is successfully initialized. + * @param funcOnInit Function to call. + */ + void attachOnInit(void (*funcOnInit)(void)) { + pFuncOnInit = funcOnInit; + }; + /**@}*/ + + bool AMBXConnected; + +protected: + /** Pointer to USB class instance. */ + USB *pUsb; + /** Device address. */ + uint8_t bAddress; + /** Endpoint info structure. */ + EpInfo epInfo[AMBX_MAX_ENDPOINTS]; + +private: + /** + * Called when the AMBX controller is successfully initialized. + */ + void onInit(); + void (*pFuncOnInit)(void); // Pointer to function called in onInit() + + uint8_t writeBuf[AMBX_EP_MAXPKTSIZE]; // General purpose buffer for output data + + /* Private commands */ + void Light_Command(uint8_t *data, uint16_t nbytes); +}; + +#endif diff --git a/AMBXEnums.h b/AMBXEnums.h new file mode 100644 index 00000000..e3c4c5ca --- /dev/null +++ b/AMBXEnums.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2021 Aran Vink. All rights reserved. + + This software may be distributed and modified under the terms of the GNU + General Public License version 2 (GPL2) as published by the Free Software + Foundation and appearing in the file GPL2.TXT included in the packaging of + this file. Please note that GPL2 Section 2[b] requires that all works based + on this software must also be made publicly available under the terms of + the GPL2 ("Copyleft"). + + Contact information + ------------------- + + Aran Vink + e-mail : aranvink@gmail.com + */ + +#ifndef _ambxenums_h +#define _ambxenums_h + +/** Used to set the colors of the AMBX lights. This is just a limited predefined set, the lights allow ANY value between 0x00 and 0xFF */ +enum AmbxColorsEnum { + Red = 0xFF0000, + Green = 0x00FF00, + Blue = 0x0000FF, + White = 0xFFFFFF, + Off = 0x000000, +}; + +/** Used to select light in the AMBX system */ +enum AmbxLightsEnum { + Sidelight_left = 0x0B, + Sidelight_right = 0x1B, + Wallwasher_left = 0x2B, + Wallwasher_center = 0x3B, + Wallwasher_right = 0x4B, +}; + +#endif diff --git a/examples/ambx/AMBX.ino b/examples/ambx/AMBX.ino new file mode 100644 index 00000000..e1eb9147 --- /dev/null +++ b/examples/ambx/AMBX.ino @@ -0,0 +1,55 @@ +/* + Example sketch for the AMBX library - developed by Aran Vink + */ + +#include + +// Satisfy the IDE, which needs to see the include statment in the ino too. +#ifdef dobogusinclude +#include +#endif +#include + +USB Usb; +AMBX AMBX(&Usb); // This will just create the instance + +bool printAngle; +uint8_t state = 0; + +void setup() { + Serial.begin(115200); +#if !defined(__MIPSEL__) + while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection +#endif + if (Usb.Init() == -1) { + Serial.print(F("\r\nOSC did not start")); + while (1); //halt + } + Serial.print(F("\r\nAMBX USB Library Started")); +} +void loop() { + Usb.Task(); + + if (AMBX.AMBXConnected) { // One can only set the color of the bulb, set the rumble, set and get the bluetooth address and calibrate the magnetometer via USB + if (state == 0) { + + } else if (state == 1) { + AMBX.setAllLights(Red); + } else if (state == 2) { + AMBX.setAllLights(Green); + } else if (state == 3) { + AMBX.setAllLights(Blue); + } else if (state == 4) { + AMBX.setAllLights(White); + } + + //Example using single light: + //AMBX.setLight(Wallwasher_center, White); + + state++; + if (state > 4) + state = 0; + delay(1000); + } + delay(10); +}