diff --git a/CamParser.cpp b/CamParser.cpp new file mode 100644 index 0000000..4ec8142 --- /dev/null +++ b/CamParser.cpp @@ -0,0 +1,70 @@ +#include "CamParser.h" +#include "FSH.h" +#include "IO_EXSensorCAM.h" + + +VPIN EXSensorCAM::CAMBaseVpin = 0; + +bool CamParser::parseN(Print * stream, byte paramCount, int16_t p[]) +{ + (void)stream; // probably unused parameter + + if (EXSensorCAM::CAMBaseVpin==0) return false; // no cam found + if (paramCount == 0) return false; + VPIN vpin=EXSensorCAM::CAMBaseVpin; + byte camop=p[0]; // cam oprerator (F is special) + if (camop!='F') camop=p[0]-0x20; // lower case the oprerator + int16_t param1; + int16_t param2; + + switch(paramCount) { + case 0: + return false; + + case 1: + if (strchr_P((const char *)F("egrvwxFimt"),camop) == nullptr) return false; + param1=0; + param2=0; + break; + + case 2: // + if(camop=='c'){ + EXSensorCAM::CAMBaseVpin=p[1]; + DIAG(F("CAM base vpin: %c %d "),p[0],p[1]); + return true; + } + if (strchr_P((const char *)F("oalnrsuvimt"),camop) == nullptr) return false; + + param1 = p[1]; + param2 = 0; + break; + + case 3: // + if (p[1]>236 || p[1]<0) return false; + if (p[2]>316 || p[2]<0) return false; + + camop='A'; // sepcial case in IO_SensorCAM + vpin = p[0]; + param1 = p[1]; + param2 = p[2]; + break; + + case 4: // + if (camop!='a') return false;//must start with 'a' + + if (p[1]>80 || p[1]<0) return false; + if (p[2]>236 || p[2]<0) return false; + if (p[3]>316 || p[3]<0) return false; + + camop=128+p[1]; // sensor id in camop + param1=p[2]; // row + param2=p[3]; // col + break; + + default: + return false; + } + DIAG(F("Cam: %d %d %c %d"),vpin,param1,camop,param2); + IODevice::writeAnalogue(vpin,param1,camop,param2); + return true; +} diff --git a/CamParser.h b/CamParser.h new file mode 100644 index 0000000..ad1b7e6 --- /dev/null +++ b/CamParser.h @@ -0,0 +1,12 @@ +#ifndef CamParser_H +#define CamParser_H +#include +#include "IODevice.h" + +class CamParser { + public: + static bool parseN(Print * stream, byte paramCount, int16_t p[]); +}; + + +#endif \ No newline at end of file diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 431093f..cdbd71a 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -117,6 +117,7 @@ Once a new OPCODE is decided upon, update this list. #include "Turntables.h" #include "version.h" #include "KeywordHasher.h" +#include "CamParser.h" // This macro can't be created easily as a portable function because the // flashlist requires a far pointer for high flash access. @@ -401,7 +402,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else IODevice::write(-p[0],LOW); return; } - if (params>=2 && params<=4) { // + if (params>=2 && params<=4) { // // unused params default to 0 IODevice::writeAnalogue(p[0],p[1],p[2],p[3]); return; @@ -799,7 +800,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) return; break; #endif - +#ifndef IO_NO_HAL + case 'N': // if not intercepted by EXRAIL diff --git a/IODevice.h b/IODevice.h index 6c70f5f..2ba0355 100644 --- a/IODevice.h +++ b/IODevice.h @@ -547,6 +547,7 @@ protected: #include "IO_duinoNodes.h" #include "IO_EXIOExpander.h" #include "IO_trainbrains.h" +#include "IO_EXSensorCAM.h" #endif // iodevice_h diff --git a/IO_EXSensorCAM.h b/IO_EXSensorCAM.h new file mode 100644 index 0000000..81a03ef --- /dev/null +++ b/IO_EXSensorCAM.h @@ -0,0 +1,208 @@ +/* 12/MAY/24 + * © 2022, Peter Cole. All rights reserved. + * © 2023, Barry Daniel ESP32 revision + * © 2024, Harald Barth. All rights reserved. + * + * This file is part of EX-CommandStation + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +/* + * The IO_EXSensorCAM.h device driver can integrate with the sensorCAM device. + * This device driver will configure the device on startup, along with + * interacting with the device for all input/output duties. + * + * To create EX-SensorCAM devices, define them in myAutomation.h: + * + * // HAL(EXSensorCAM,vpin, num_vpins, i2c_address); + * HAL(EXSensorCAM,700, 80, 0x11) + * + * The total number of pins cannot exceed 80 because of the communications + * packet format. The number of analogue inputs cannot exceed 16 because of a + * limit on the maximum I2C packet size of 32 bytes (in the Wire library). + */ + +#ifndef IO_EX_EXSENSORCAM_H +#define IO_EX_EXSENSORCAM_H + +#include "DIAG.h" +#include "FSH.h" +#include "I2CManager.h" +#include "IODevice.h" +#include "CamParser.h" +///////////////////////////////////////////////////////////////////////////////////////////////////// +/* + * IODevice subclass for EX-SensorCAM. + */ +class EXSensorCAM : public IODevice { + public: + static void create(VPIN vpin, int nPins, I2CAddress i2cAddress) { + if (checkNoOverlap(vpin, nPins, i2cAddress)) + new EXSensorCAM(vpin, nPins, i2cAddress); + } + static VPIN CAMBaseVpin; + private: + // Constructor + EXSensorCAM(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { + _firstVpin = firstVpin; + if (CAMBaseVpin==0) CAMBaseVpin=_firstVpin; + // Number of pins cannot exceed 80. + if (nPins > 80) nPins = 80; + _nPins = nPins; + _I2CAddress = i2cAddress; + addDevice(this); + } + + void _begin() { + // Initialise EX-SensorCAM device + I2CManager.begin(); + + if (!I2CManager.exists(_I2CAddress)) { + DIAG(F("EX-SensorCAM I2C:%s device not found"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + return; + } + + _i2crb.setRequestParams(_I2CAddress, _inputBuffer, sizeof(_inputBuffer), + _outputBuffer, sizeof(_outputBuffer)); + _outputBuffer[0]='V'; + _i2crb.writeLen=1; + I2CManager.queueRequest(&_i2crb); + _deviceState = DEVSTATE_SCANNING; + } + + + // Main loop, collect any input and reissue poll cmd + void _loop(unsigned long currentMicros) override { + if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return + + if (_i2crb.isBusy()) return; // If I2C operation still in progress, return + + processInput(); + if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return + + + // is it time to request another set of pins? + if (currentMicros - _lastDigitalRead > _digitalRefresh) return; + + // Issue new read request for digital states. + _lastDigitalRead = currentMicros; + _outputBuffer[0] = 'Q'; // query sensor states + _i2crb.writeLen = 1; + I2CManager.queueRequest(&_i2crb); + _deviceState = DEVSTATE_SCANNING; + } + + // Obtain the a block of 8 sensors as an analog value. + // can be used to track poisition in sequention pin blocks + int _readAnalogue(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + return _digitalInputStates[(vpin - _firstVpin) / 8]; + } + + // Obtain a sensor state + int _read(VPIN vpin) override { + if (_deviceState == DEVSTATE_FAILED) return 0; + int pin = vpin - _firstVpin; + return bitRead(_digitalInputStates[pin / 8], pin % 8); + } + + // Used to write a command from the parseN function. + // note parameter names changed to suit use in this scenario + // but the function signature remians as per IO_DEVICE + // _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override { + void _writeAnalogue(VPIN vpin, int p1, uint8_t opcode, uint16_t p2) override { + if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return + _i2crb.wait(); + // opcodes are lower case as-entered by user through CamParser. + // except as follows: (to avoid adding more base functions to IO_DEVICE) + // is passed in here as opcode=128+bsNo, p1=row, p2=col + // so p0 is reverse engineered from the opcode + // is passed with opcode 'A' p1=row, p2=col + // and is converted to 'a' and the P0 bsNo obtained from the VPIN. + uint16_t p0 =0; + + if (opcode>=128) { // + p0=opcode & 0x7f; // get bSno from opcode + opcode='a'; // and revert to correct code + } + else if (opcode=='A') { // + p0=vpin - _firstVpin; + opcode='a'; + } + + _outputBuffer[0]=opcode; + _outputBuffer[1]=p0 & 0xFF; + _outputBuffer[2]=p0 >> 8; + _outputBuffer[3]=p1 & 0xFF; + _outputBuffer[4]=(p1 >> 8) & 0xFF; + _outputBuffer[5]=p2 & 0xFF; + _outputBuffer[6]=p2 >> 8; + I2CManager.queueRequest(&_i2crb); + } + + // This function is invoked when an I/O operation on the requestBlock + // completes. + void processInput() { + if (_deviceState != DEVSTATE_SCANNING) return; + // some input was pending + uint8_t status = _i2crb.status; + if (status != I2C_STATUS_OK) { + reportError(status); + return; + } + + _deviceState = DEVSTATE_NORMAL; + + if (_inputBuffer[0] == 'Q') { // all sensors + memcpy(_digitalInputStates, _inputBuffer + 1, + sizeof(_digitalInputStates)); + return; + } + if (_inputBuffer[0] == 'V') { // version response + memcpy(_version, _inputBuffer + 1, sizeof(_version)); + _display(); + return; + } + } + + // Display device information and status. + void _display() override { + DIAG(F("EX-SensorCAM I2C:%s v%d.%d.%d Vpins %u-%u %S"), + _I2CAddress.toString(), _version[0], _version[1], _version[2], + (int)_firstVpin, (int)_firstVpin + _nPins - 1, + _deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F("")); + } + + // Helper function for error handling + void reportError(uint8_t status, bool fail = true) { + DIAG(F("EX-SensorCAM I2C:%s Error:%d (%S)"), _I2CAddress.toString(), status, + I2CManager.getErrorMessage(status)); + if (fail) _deviceState = DEVSTATE_FAILED; + } + + uint8_t _numDigitalPins = 80; + uint8_t _version[3]; + uint8_t _digitalInputStates[10]; + I2CRB _i2crb; + byte _inputBuffer[12]; + byte _outputBuffer[8]; + + unsigned long _lastDigitalRead = 0; + const unsigned long _digitalRefresh = + 10000UL; // Delay refreshing digital inputs for 10ms +}; + +#endif