#ifdef CANDRV #include #include #include #include #if defined(ARDUINO_ARCH_STM32) #include STM32_CAN STMCan( CAN1, CAN_PINS::DEF, RX_SIZE_64, TX_SIZE_16 ); #endif #if defined(ARDUINO_ARCH_ESP32) #include #endif #if defined(__SAM3X8E__) #include #endif #include //#include extern systemConfig sysConf; extern canStream CANConfStream; extern aJsonObject * root; extern volatile int8_t configLocked; extern bool configLoaded; /** * @brief Prints the data contained in a CAN frame. * * @param frame Pointer to the datagram_t structure containing the CAN frame. * @param len Length of the data to print. */ void printFrame(datagram_t * frame, uint8_t len ) { debugSerial.print(" Data:"); for (int count = 0; count < len; count++) { debugSerial.print(frame->data[count], HEX); debugSerial.print(" "); } debugSerial.println(); } /** * @brief Sends the uptime metric to the CAN bus. * * @param ut Uptime value to send. * @return true if the message was sent successfully, false otherwise. */ bool canDriver::upTime(uint32_t ut) { if (!controllerId) return false; canid_t id; datagram_t packet; id.reserve=0; id.status=1; id.payloadType=payloadType::metric; id.deviceId=controllerId; id.subjId=metricType::UpTime; packet.metric1=ut; debugSerial<<("CAN: UpTime")<=8) break; } RXid.id = CAN.packetId(); debugSerialPort.println(); return RXlen; } } #endif //DUE #if defined(__SAM3X8E__) CAN_FRAME CAN_RX_msg; if (Can0.available() > 0) { Can0.read(CAN_RX_msg); if (CAN_RX_msg.length>8) CAN_RX_msg.length=8; memcpy(RXpacket.data, CAN_RX_msg.data.bytes,CAN_RX_msg.length); RXlen = CAN_RX_msg.length; RXid.id = CAN_RX_msg.id; return RXlen; } #endif return -1; } /** * @brief Polls the CAN bus for incoming frames and processes them. */ void canDriver::Poll() { if (readFrame()>=0) processPacket( RXid, &RXpacket, RXlen); //State machine switch (state) { case canState::MACLookup: // case canState::FrameRequested: if (isTimeOver(responseTimer,millis(),1000UL)) { responseTimer=millisNZ(); state=canState::Error; errorSerial<<"CAN: lookup Timeout"<mac, sysConf.mac,6))) //Ignore responses for another controller { debugSerial.println(")"); return false; } break; case payloadType::configFrame: debugSerial.print("configFrame #"); debugSerial<< id.subjId; if (id.status && (id.deviceId != controllerId)) //Ignore responses on config request not for me { debugSerial.println(")"); return false; } break; case payloadType::OTAFrame: debugSerial.print("OTAFrame #"); debugSerial<< id.subjId; break; case payloadType::auth: debugSerial.print("auth #"); debugSerial<< id.subjId; break; case payloadType::metric: debugSerial.print("metric #"); debugSerial<< id.subjId; break; case payloadType::sysCmd: debugSerial.print("sysCmd"); if (id.deviceId != controllerId) //Ignore commands not for me { debugSerial.println(")"); return false; } break; case payloadType::rawPinCtrl: debugSerial.print("rawPinCtrl"); if (id.deviceId != controllerId) //Ignore commands not for me { debugSerial.println(")"); return false; } } debugSerial<< ") "; if (len) printFrame(packet,len); if (id.status){ //Responces //@ any state switch (id.payloadType) { case payloadType::itemCommand: { if (len!=8) return false; aJsonObject *confObj = getConfbyID(id.deviceId); if (confObj) { debugSerial<cmd; ic.param = packet->param; debugSerial<name<<" "; ic.debugOut(); if (ic.isValue()) flags |= FLAG_PARAMETERS; if (ic.getSuffix()==S_DELAYED) flags |= FLAG_SEND_DELAYED; else if (ic.isCommand()) flags |= FLAG_COMMAND; ic.saveItem(&it,flags); it.SendStatusImmediate(ic,flags | FLAG_NOT_SEND_CAN, it.getSubItemStrById(id.subItemId)); return true; } } } break; } switch (state) { case canState::MACLookup: if ((id.payloadType == payloadType::lookupMAC)) { if (root && (id.subjId == confCRC)) ///? { infoSerial << (F("Valid config already onboard")) << endl; state = canState::Idle; } else { infoSerial<<"\nCAN: Got Controller addr: "<cmd; ic.param = packet->param; //debugSerial<=6)) { return sendRemoteID(packet->mac); //debugSerial<<"ID requested"<type == aJson_Int)) return addrObj->valueint; return 0; } /** * @brief Retrieves the configuration object for a device by its ID. * * @param devId Device ID to look up. * @return Pointer to the configuration object, or NULL if not found. */ aJsonObject * canDriver::getConfbyID(uint8_t devId) { if (!canConfigObj) return NULL; if (!canRemoteConfigObj || canRemoteConfigObj->type != aJson_Object) return NULL; aJsonObject * remoteConfObj=canRemoteConfigObj->child; while (remoteConfObj) { aJsonObject * remoteCanObj = aJson.getObjectItem(remoteConfObj, "can"); if (remoteCanObj) { aJsonObject * addrObj = aJson.getObjectItem(remoteCanObj, "addr"); if (addrObj && (addrObj->type == aJson_Int) && (addrObj->valueint == devId)) return remoteConfObj; } remoteConfObj=remoteConfObj->next; } return NULL; } /** * @brief Finds a configuration object by device name. * * @param devName Name of the device to look for. * @param devAddr Pointer to store the device address if found. * @return Pointer to the configuration object, or NULL if not found. */ aJsonObject * canDriver::findConfbyName(char* devName, int * devAddr) { if (!canRemoteConfigObj || canRemoteConfigObj->type != aJson_Object || !devName ) return NULL; aJsonObject * remoteConfObj=canRemoteConfigObj->child; while (remoteConfObj) { aJsonObject * remoteCanObj = aJson.getObjectItem(remoteConfObj, "can"); if (remoteCanObj) { aJsonObject * nameObj = aJson.getObjectItem(remoteCanObj, "name"); if (nameObj && (nameObj->type == aJson_String) && nameObj->valuestring && (strncasecmp(nameObj->valuestring,devName,strlen(nameObj->valuestring)) == 0)) { if (devAddr) { aJsonObject * addrObj = aJson.getObjectItem(remoteCanObj, "addr"); if (addrObj && (addrObj->type == aJson_Int)) *devAddr=addrObj->valueint; } return remoteConfObj; } } remoteConfObj=remoteConfObj->next; } return NULL; } #if not defined (NOIP) extern PubSubClient mqttClient; /** * @brief Subscribes to MQTT topics based on the CAN configuration. * * @param root Pointer to the root topic string. * @param buflen Length of the buffer for the topic string. * @return true if subscription was successful, false otherwise. */ bool canDriver::subscribeTopics(char * root, size_t buflen) { if (!root) return false; if (!canRemoteConfigObj || canRemoteConfigObj->type != aJson_Object) return false; int rootLen = strlen(root); aJsonObject * remoteConfObj=canRemoteConfigObj->child; while (remoteConfObj) { aJsonObject * remoteCanObj = aJson.getObjectItem(remoteConfObj, "can"); if (remoteCanObj) { aJsonObject * addrObj = aJson.getObjectItem(remoteCanObj, "name"); if (addrObj && (addrObj->type == aJson_String) && addrObj->valuestring) { strncpy(root+rootLen, addrObj->valuestring, buflen-rootLen-1); strncat(root+rootLen, "/", buflen-rootLen-1); strncat(root+rootLen, "#", buflen-rootLen-1); debugSerial.print("CAN: subscribe "); debugSerial.println(root); mqttClient.subscribe(root); } } remoteConfObj=remoteConfObj->next; } //debugSerial<<"Subscribed"<type == aJson_Int)) { debugSerial<valueint << endl; return addrObj->valueint; } return 0; } /** * @brief Sends a message over the CAN bus. * * @param msg_id Identifier for the message. * @param buf Pointer to the datagram_t structure containing the message data. * @param size Size of the message data. * @return true if the message was sent successfully, false otherwise. */ bool canDriver::write(uint32_t msg_id, datagram_t * buf, uint8_t size) { // if (!ready) { errorSerial<<"CAN: not initialized"<8) size = 8; //STM32 #if defined(ARDUINO_ARCH_STM32) //if (!Can) return 0; if (buf) for(uint8_t i=0;idata[i]; CAN_TX_msg.id = msg_id; CAN_TX_msg.flags.extended = 1; // To enable extended ID CAN_TX_msg.len=size; if (res=STMCan.write(CAN_TX_msg)) {traceSerial<<("CAN: Wrote ")<data,size); //for(uint8_t i=0;idata[i]; res=Can0.sendFrame(CAN_TX_msg); if (res) {traceSerial<type == aJson_Array)) { int subItem=NO_SUBITEM; aJsonObject * dev = aJson.getArrayItem(can,0); aJsonObject * it = aJson.getArrayItem(can,1); aJsonObject * sfx = aJson.getArrayItem(can,2); aJsonObject * subItemObj = aJson.getArrayItem(can,3); if (sfx) switch (sfx->type) { case aJson_Int: cmd.setSuffix(sfx->valueint); break; case aJson_String: int suffix=txt2subItem(sfx->valuestring); if (suffix) cmd.setSuffix(suffix); } if (subItemObj) switch (subItemObj->type) { case aJson_Int: if (subItemObj->valueint>=0 && subItemObj->valueintvalueint; break; case aJson_String: int suffix=txt2cmd(subItemObj->valuestring); if (suffix) subItem = suffix | SUBITEM_IS_COMMAND; } if (dev && it && dev->type == aJson_Int && it->type == aJson_Int) return sendCommand(dev->valueint, it->valueint, cmd, status,subItem); } return false; } /** * @brief Sends a command to a specified device by ID. * * @param devID Device ID to send the command to. * @param itemID Item ID to send the command for. * @param cmd Command structure containing the command information. * @param status Indicates if the command is a status update. * @param subItemID Sub-item identifier. * @return true if the command was sent successfully, false otherwise. */ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool status,int subItemID ) { canid_t id; datagram_t packet; bool res; id.reserve=0; id.status=status?1:0; id.payloadType=payloadType::itemCommand; id.deviceId=devID; id.itemId=itemID; id.subItemId=subItemID; packet.cmd = cmd.cmd; packet.param = cmd.param; debugSerial << ((status)?"CAN: Send Status":"CAN: Send Command"); debugSerial<[")<write (id.id, &writeBuffer, len); writePos=0; if (res) { //Await check? return 1; } else return 0; } /** * @brief Checks the state of the CAN stream and processes any incoming data. * * @return -1 on error, or the number of bytes available for reading. */ int canStream::checkState() { bool res = false; if (!driver) return -1; switch (state) { case canState::StreamOpenedRead: readPos = 0; res= driver->requestFrame(devId,pType,seqNo); //Requesting frame; if (res) state = canState::FrameRequested; else { state = canState::Error; return -1; } if (seqNo ==0xFFFF) seqNo =0; //continue case canState::FrameRequested: { int c; uint32_t timer = millis(); do { //debugSerial.print("."); yield(); if ((c=driver->readFrame()>0) && (driver->RXid.deviceId == devId) && (driver ->RXid.payloadType == pType) && driver->RXid.status) { state = canState::FrameReceived; debugSerial<RXlen<< " "<RXpacket.payload<<"#"<RXid.subjId<RXid.subjId; failedCount=0; return driver->RXlen; } } while((!isTimeOver(timer,millis(),1000UL)) ); debugSerial<=3) { state = canState::Error; return -1; } failedCount++; state = canState::StreamOpenedRead; } return -1; break; case canState::FrameReceived: return driver->RXlen; break; case canState::waitingConfirm: if (driver->readFrame()>=0) { if ( (driver->RXid.deviceId == devId) && (driver->RXid.payloadType == pType) && (driver->RXid.status == 0) ) { debugSerial<RXid.subjId<RXid.subjId) { state = canState::StreamOpenedWrite; seqNo++; } else { state = canState::Error; errorSerial<RXlen; break; case canState::Idle: return -1; break; } return -1; }; // Stream methods /** * @brief Checks how many bytes are available for reading from the CAN stream. * * @return Number of bytes available, or -1 on error. */ int canStream::available() { if (!driver) return -1; int avail = checkState(); return avail; }; /** * @brief Reads a byte from the CAN stream. * * @return The byte read, or -1 on error. */ int canStream::read() { if (!driver) return -1; int avail = checkState(); int ch; if (avail>=0) { ch = driver->RXpacket.data[readPos++]; if (readPos>=8) state = canState::StreamOpenedRead; return ch; } else return -1; }; /** * @brief Peeks at the next byte in the CAN stream without removing it. * * @return The next byte, or -1 on error. */ int canStream::peek() { if (!driver) return -1; int avail = checkState(); int ch; if (avail>=0) { ch = driver->RXpacket.data[readPos]; return ch; } else return -1; }; /** * @brief Writes a byte to the CAN stream. * * @param c The byte to write. * @return The number of bytes written, or -1 on error. */ size_t canStream::write(uint8_t c) { //if ((state != canState::StreamOpenedWrite) || (state != canState::waitingConfirm)) return -1; uint32_t timer = millis(); do { checkState(); yield(); //debugSerial.print("*"); if (isTimeOver(timer,millis(),1000UL)) { state = canState::Error; errorSerial<=8) { bool res = send(8,seqNo); if (res) state = canState::waitingConfirm; else state = canState::Error; return res; } return 1; }; /** * @brief Flushes the CAN stream, sending any buffered data. */ void canStream::flush() { send(writePos,seqNo); }; /** * @brief Checks if the CAN stream is available for writing. * * @return 1 if available, 0 if waiting for confirmation. */ int canStream::availableForWrite() { switch (state) { case canState::waitingConfirm: return 0; } return 1; } #endif