input fix for mixed configs & repeat flag, comments

This commit is contained in:
2025-08-23 23:26:05 +03:00
parent 5294617455
commit 2c601b2c03
5 changed files with 326 additions and 44 deletions

View File

@@ -18,7 +18,7 @@
-DMULTIVENT_DISABLE -DMULTIVENT_DISABLE
#-DWiz5100 #-DWiz5100
-DARDUINO_OTA_MDNS_DISABLE -DARDUINO_OTA_MDNS_DISABLE
-DMDNS_ENABLE #-DMDNS_ENABLE
-DHSV_DISABLE -DHSV_DISABLE
-DPWM_DISABLE -DPWM_DISABLE

View File

@@ -27,6 +27,12 @@ extern volatile int8_t configLocked;
extern bool configLoaded; 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 ) { void printFrame(datagram_t * frame, uint8_t len ) {
debugSerial.print(" Data:"); debugSerial.print(" Data:");
@@ -38,6 +44,12 @@ void printFrame(datagram_t * frame, uint8_t len ) {
} }
/**
* @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) bool canDriver::upTime(uint32_t ut)
{ {
if (!controllerId) return false; if (!controllerId) return false;
@@ -57,6 +69,12 @@ bool canDriver::upTime(uint32_t ut)
return write (id.id, &packet, 4); return write (id.id, &packet, 4);
} }
/**
* @brief Sends the salt metric to the CAN bus.
*
* @param salt Salt value to send.
* @return true if the message was sent successfully, false otherwise.
*/
bool canDriver::salt(uint32_t salt) bool canDriver::salt(uint32_t salt)
{ {
if (!controllerId) return false; if (!controllerId) return false;
@@ -77,6 +95,11 @@ bool canDriver::salt(uint32_t salt)
return write (id.id, &packet, 4); return write (id.id, &packet, 4);
} }
/**
* @brief Looks up the MAC address and sends it over the CAN bus.
*
* @return true if the MAC address was sent successfully, false otherwise.
*/
bool canDriver::lookupMAC() bool canDriver::lookupMAC()
{ {
// return 0; // return 0;
@@ -102,6 +125,14 @@ bool canDriver::lookupMAC()
return res; return res;
} }
/**
* @brief Requests a frame from the specified device on the CAN bus.
*
* @param devId Device ID to request the frame from.
* @param _payloadType Type of payload to request.
* @param seqNo Sequence number for the request.
* @return true if the request was successful, false otherwise.
*/
bool canDriver::requestFrame(uint8_t devId, payloadType _payloadType, uint16_t seqNo ) bool canDriver::requestFrame(uint8_t devId, payloadType _payloadType, uint16_t seqNo )
{ {
canid_t id; canid_t id;
@@ -127,6 +158,12 @@ packet.metric1 =0;
return res; return res;
} }
/**
* @brief Sends the remote ID of a device identified by its MAC address.
*
* @param mac MAC address of the device.
* @return true if the remote ID was sent successfully, false otherwise.
*/
bool canDriver::sendRemoteID(macAddress mac) bool canDriver::sendRemoteID(macAddress mac)
{ {
canid_t id; canid_t id;
@@ -154,6 +191,11 @@ return res;
} }
/**
* @brief Initializes the CAN driver and sets up the CAN bus.
*
* @return true if initialization was successful, false otherwise.
*/
bool canDriver::begin() bool canDriver::begin()
{ {
if (root) if (root)
@@ -207,6 +249,11 @@ bool canDriver::begin()
return true; return true;
} }
/**
* @brief Reads a frame from the CAN bus.
*
* @return Length of the received frame, or -1 if no frame was received.
*/
int canDriver::readFrame() int canDriver::readFrame()
{ {
if (!ready) return -1; if (!ready) return -1;
@@ -284,6 +331,9 @@ int canDriver::readFrame()
return -1; return -1;
} }
/**
* @brief Polls the CAN bus for incoming frames and processes them.
*/
void canDriver::Poll() void canDriver::Poll()
{ {
@@ -355,6 +405,15 @@ switch (state)
} }
} }
/**
* @brief Processes a received CAN packet.
*
* @param id Identifier of the CAN packet.
* @param packet Pointer to the received datagram_t structure.
* @param len Length of the received packet.
* @param rtr Indicates if the packet is a remote transmission request.
* @return true if the packet was processed successfully, false otherwise.
*/
bool canDriver::processPacket(canid_t id, datagram_t *packet, uint8_t len, bool rtr) bool canDriver::processPacket(canid_t id, datagram_t *packet, uint8_t len, bool rtr)
{ {
@@ -557,6 +616,11 @@ else //Requests
return false; return false;
} }
/**
* @brief Retrieves the device ID of this CAN driver.
*
* @return The device ID, or 0 if not set.
*/
uint8_t canDriver::getMyId() uint8_t canDriver::getMyId()
{ {
if (!canConfigObj) return 0; if (!canConfigObj) return 0;
@@ -565,6 +629,12 @@ if (addrObj && (addrObj->type == aJson_Int)) return addrObj->valueint;
return 0; 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) aJsonObject * canDriver::getConfbyID(uint8_t devId)
{ {
if (!canConfigObj) return NULL; if (!canConfigObj) return NULL;
@@ -583,6 +653,13 @@ while (remoteConfObj)
return NULL; 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) aJsonObject * canDriver::findConfbyName(char* devName, int * devAddr)
{ {
if (!canRemoteConfigObj || canRemoteConfigObj->type != aJson_Object || !devName ) return NULL; if (!canRemoteConfigObj || canRemoteConfigObj->type != aJson_Object || !devName ) return NULL;
@@ -611,6 +688,13 @@ return NULL;
#if not defined (NOIP) #if not defined (NOIP)
extern PubSubClient mqttClient; 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) bool canDriver::subscribeTopics(char * root, size_t buflen)
{ {
if (!root) return false; if (!root) return false;
@@ -643,6 +727,12 @@ while (remoteConfObj)
} }
#endif #endif
/**
* @brief Retrieves the device ID associated with a given MAC address.
*
* @param mac MAC address to look up.
* @return The device ID, or 0 if not found.
*/
uint8_t canDriver::getIdByMac(macAddress mac) uint8_t canDriver::getIdByMac(macAddress mac)
{ {
char macStr[19]; char macStr[19];
@@ -675,6 +765,14 @@ if (addrObj && (addrObj->type == aJson_Int))
return 0; 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) bool canDriver::write(uint32_t msg_id, datagram_t * buf, uint8_t size)
{ // { //
if (!ready) { if (!ready) {
@@ -724,12 +822,28 @@ bool canDriver::write(uint32_t msg_id, datagram_t * buf, uint8_t size)
/**
* @brief Sends the status of a specified item.
*
* @param itemNum Item number to send the status for.
* @param cmd Command structure containing the status information.
* @param subItem Sub-item identifier.
* @return true if the status was sent successfully, false otherwise.
*/
bool canDriver::sendStatus(uint16_t itemNum, itemCmd cmd, int subItem) bool canDriver::sendStatus(uint16_t itemNum, itemCmd cmd, int subItem)
{ {
if (!itemNum || !controllerId) return false; if (!itemNum || !controllerId) return false;
return sendCommand(controllerId, itemNum, cmd, true, subItem); return sendCommand(controllerId, itemNum, cmd, true, subItem);
} }
/**
* @brief Sends a command to a specified CAN device.
*
* @param can Pointer to the configuration object for the device.
* @param cmd Command structure containing the command information.
* @param status Indicates if the command is a status update.
* @return true if the command was sent successfully, false otherwise.
*/
bool canDriver::sendCommand(aJsonObject * can, itemCmd cmd, bool status) bool canDriver::sendCommand(aJsonObject * can, itemCmd cmd, bool status)
{ {
if (can && (can->type == aJson_Array)) if (can && (can->type == aJson_Array))
@@ -766,6 +880,16 @@ bool canDriver::sendCommand(aJsonObject * can, itemCmd cmd, bool status)
return false; 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 ) bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool status,int subItemID )
{ {
canid_t id; canid_t id;
@@ -801,6 +925,13 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
////////////////////////////// Steream ////////////////////////// ////////////////////////////// Steream //////////////////////////
/**
* @brief Sends data over the CAN stream.
*
* @param len Length of the data to send.
* @param _seqNo Sequence number for the data.
* @return 1 if the data was sent successfully, 0 otherwise.
*/
int canStream::send(uint8_t len, uint16_t _seqNo) int canStream::send(uint8_t len, uint16_t _seqNo)
{ {
canid_t id; canid_t id;
@@ -823,6 +954,11 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
else return 0; 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() int canStream::checkState()
{ {
bool res = false; bool res = false;
@@ -918,6 +1054,11 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
// Stream methods // 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() int canStream::available()
{ {
if (!driver) return -1; if (!driver) return -1;
@@ -925,6 +1066,11 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
return avail; return avail;
}; };
/**
* @brief Reads a byte from the CAN stream.
*
* @return The byte read, or -1 on error.
*/
int canStream::read() int canStream::read()
{ {
if (!driver) return -1; if (!driver) return -1;
@@ -939,6 +1085,11 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
else return -1; 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() int canStream::peek()
{ {
if (!driver) return -1; if (!driver) return -1;
@@ -954,6 +1105,12 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
/**
* @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) size_t canStream::write(uint8_t c)
{ {
//if ((state != canState::StreamOpenedWrite) || (state != canState::waitingConfirm)) return -1; //if ((state != canState::StreamOpenedWrite) || (state != canState::waitingConfirm)) return -1;
@@ -984,12 +1141,20 @@ bool canDriver::sendCommand(uint8_t devID, uint16_t itemID,itemCmd cmd, bool sta
} }
return 1; }; return 1; };
/**
* @brief Flushes the CAN stream, sending any buffered data.
*/
void canStream::flush() void canStream::flush()
{ {
send(writePos,seqNo); 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() int canStream::availableForWrite()
{ {
switch (state) switch (state)

View File

@@ -681,9 +681,9 @@ debugSerial << F("IN:") << pin << F(" DHT22 type. T=") << temp << F("°C H=") <<
return false; return false;
} }
// TODO Polling via timed interrupt with CHECK_INTERRUPT cause
bool Input:: bool Input::
changeState(uint8_t newState, short cause, aJsonObject * currentInputObject, bool contactState) changeState(uint8_t newState, short cause, aJsonObject * currentInputObject, bool contactState, bool calledOnTimer)
{ {
if (!inputObj || !store) return false; if (!inputObj || !store) return false;
@@ -782,22 +782,30 @@ if (newState!=store->state && cause!=CHECK_INTERRUPT) debugSerial<<F("#")<<pin<<
cmd = aJson.getObjectItem(currentInputObject, "rpcmd3"); cmd = aJson.getObjectItem(currentInputObject, "rpcmd3");
toggle=store->toggle3; toggle=store->toggle3;
break; break;
case IS_NOP:
if (!calledOnTimer) break;
if (contactState)
cmd = aJson.getObjectItem(currentInputObject, "scmd");
else cmd = aJson.getObjectItem(currentInputObject, "rcmd");
break;
} }
if (cause != CHECK_INTERRUPT) if (!calledOnTimer || newState == IS_NOP)
{ {
onContactChanged(contactState); if (cause != CHECK_INTERRUPT)
store->delayedState=false; {
} onContactChanged(contactState);
else store->delayedState=false;
{ }
store->delayedState=true; else
store->lastValue = contactState; {
store->reqState=newState; store->delayedState=true;
store->lastValue = contactState;
store->reqState=newState;
}
} }
if (newState == IS_NOP) return true; if ((newState == IS_NOP) && !calledOnTimer) return true;
aJsonObject *defaultItem = aJson.getObjectItem(currentInputObject, "item"); aJsonObject *defaultItem = aJson.getObjectItem(currentInputObject, "item");
aJsonObject *defaultEmit = aJson.getObjectItem(currentInputObject, "emit"); aJsonObject *defaultEmit = aJson.getObjectItem(currentInputObject, "emit");
@@ -807,7 +815,7 @@ if (newState!=store->state && cause!=CHECK_INTERRUPT) debugSerial<<F("#")<<pin<<
if (!cmd && !defCmd.isCommand()) if (!cmd && !defCmd.isCommand())
{ {
store->state=newState; if (newState !=IS_NOP) store->state=newState;
store->delayedState=false; store->delayedState=false;
return true; //nothing to do return true; //nothing to do
} }
@@ -815,7 +823,7 @@ if (newState!=store->state && cause!=CHECK_INTERRUPT) debugSerial<<F("#")<<pin<<
if (cause != CHECK_INTERRUPT) if (cause != CHECK_INTERRUPT)
{ {
store->state=newState; if (newState !=IS_NOP) store->state=newState;
store->delayedState=false; store->delayedState=false;
checkInstructions(cmd); checkInstructions(cmd);
executeCommand(cmd,toggle,defCmd,defaultItem,defaultEmit,defaultCan); executeCommand(cmd,toggle,defCmd,defaultItem,defaultEmit,defaultCan);
@@ -824,7 +832,7 @@ if (newState!=store->state && cause!=CHECK_INTERRUPT) debugSerial<<F("#")<<pin<<
else else
{ {
//Postpone actual execution //Postpone actual execution
if (newState != store->state) if ((newState != store->state) && (newState !=IS_NOP))
{ {
store->reqState=newState; store->reqState=newState;
store->delayedState=true; store->delayedState=true;
@@ -880,15 +888,15 @@ switch (store->state) //Timer based transitions
case IS_PRESSED: case IS_PRESSED:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF))
{ {
if (!aJson.getObjectItem(inputObj, "lcmd") && !aJson.getObjectItem(currentInputObject, "rpcmd")) changeState(IS_WAITRELEASE, cause,currentInputObject,currentInputState); if (!aJson.getObjectItem(inputObj, "lcmd") && !aJson.getObjectItem(currentInputObject, "rpcmd")) changeState(IS_WAITRELEASE, cause,currentInputObject,currentInputState,true);
else changeState(IS_LONG, cause,currentInputObject,currentInputState); else changeState(IS_LONG, cause,currentInputObject,currentInputState,true);
} }
break; break;
case IS_LONG: case IS_LONG:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF))
{ {
changeState(IS_REPEAT, cause,currentInputObject,currentInputState); changeState(IS_REPEAT, cause,currentInputObject,currentInputState,true);
store->timestamp16 = millis() & 0xFFFF; store->timestamp16 = millis() & 0xFFFF;
} }
break; break;
@@ -896,7 +904,7 @@ switch (store->state) //Timer based transitions
case IS_REPEAT: case IS_REPEAT:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF))
{ {
changeState(IS_REPEAT, cause,currentInputObject,currentInputState); changeState(IS_REPEAT, cause,currentInputObject,currentInputState,true);
store->timestamp16 = millis() & 0xFFFF; store->timestamp16 = millis() & 0xFFFF;
} }
break; break;
@@ -904,15 +912,15 @@ switch (store->state) //Timer based transitions
case IS_PRESSED2: case IS_PRESSED2:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_LONG,0xFFFF))
{ {
if (!aJson.getObjectItem(currentInputObject, "lcmd2") && !aJson.getObjectItem(currentInputObject, "rpcmd2")) changeState(IS_WAITRELEASE, cause,currentInputObject,currentInputState); if (!aJson.getObjectItem(currentInputObject, "lcmd2") && !aJson.getObjectItem(currentInputObject, "rpcmd2")) changeState(IS_WAITRELEASE, cause,currentInputObject,currentInputState,true);
else changeState(IS_LONG2, cause,currentInputObject,currentInputState); else changeState(IS_LONG2, cause,currentInputObject,currentInputState,true);
} }
break; break;
case IS_LONG2: case IS_LONG2:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF))
{ {
changeState(IS_REPEAT2, cause,currentInputObject,currentInputState); changeState(IS_REPEAT2, cause,currentInputObject,currentInputState,true);
store->timestamp16 = millis() & 0xFFFF; store->timestamp16 = millis() & 0xFFFF;
} }
break; break;
@@ -920,7 +928,7 @@ switch (store->state) //Timer based transitions
case IS_REPEAT2: case IS_REPEAT2:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF))
{ {
changeState(IS_REPEAT2, cause,currentInputObject,currentInputState); changeState(IS_REPEAT2, cause,currentInputObject,currentInputState,true);
store->timestamp16 = millis() & 0xFFFF; store->timestamp16 = millis() & 0xFFFF;
} }
break; break;
@@ -930,17 +938,17 @@ switch (store->state) //Timer based transitions
{ {
if (!aJson.getObjectItem(currentInputObject, "lcmd3") && !aJson.getObjectItem(currentInputObject, "rpcmd3")) //No longpress handlers if (!aJson.getObjectItem(currentInputObject, "lcmd3") && !aJson.getObjectItem(currentInputObject, "rpcmd3")) //No longpress handlers
{ {
if (aJson.getObjectItem(currentInputObject, "scmd3")) changeState(IS_WAITRELEASE, cause,currentInputObject,currentInputState); //was used if (aJson.getObjectItem(currentInputObject, "scmd3")) changeState(IS_WAITRELEASE, cause,currentInputObject,currentInputState,true); //was used
else changeState(IS_PRESSED2, cause,currentInputObject,currentInputState); // completely empty trippleClick section - fallback to first click handler else changeState(IS_PRESSED2, cause,currentInputObject,currentInputState,true); // completely empty trippleClick section - fallback to first click handler
} }
else changeState(IS_LONG3, cause,currentInputObject,currentInputState); else changeState(IS_LONG3, cause,currentInputObject,currentInputState,true);
} }
break; break;
case IS_LONG3: case IS_LONG3:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT,0xFFFF))
{ {
changeState(IS_REPEAT3, cause,currentInputObject,currentInputState); changeState(IS_REPEAT3, cause,currentInputObject,currentInputState,true);
store->timestamp16 = millis() & 0xFFFF; store->timestamp16 = millis() & 0xFFFF;
} }
break; break;
@@ -948,7 +956,7 @@ switch (store->state) //Timer based transitions
case IS_REPEAT3: case IS_REPEAT3:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF)) if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_RPT_PULSE,0xFFFF))
{ {
changeState(IS_REPEAT3, cause,currentInputObject,currentInputState); changeState(IS_REPEAT3, cause,currentInputObject,currentInputState,true);
store->timestamp16 = millis() & 0xFFFF; store->timestamp16 = millis() & 0xFFFF;
} }
break; break;
@@ -958,7 +966,7 @@ switch (store->state) //Timer based transitions
case IS_WAITPRESS: case IS_WAITPRESS:
if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_IDLE,0xFFFF)) changeState(IS_IDLE, cause,currentInputObject,currentInputState); if (isTimeOver(store->timestamp16,millis() & 0xFFFF,T_IDLE,0xFFFF)) changeState(IS_IDLE, cause,currentInputObject,currentInputState,true);
break; break;
} //switch } //switch
#ifdef ROTARYENCODER #ifdef ROTARYENCODER
@@ -1028,7 +1036,7 @@ if (re)
res = changeState(IS_PRESSED3, cause,currentInputObject,currentInputState); res = changeState(IS_PRESSED3, cause,currentInputObject,currentInputState);
break; break;
default: default:
res = changeState(IS_NOP, cause,currentInputObject,currentInputState); res = changeState(IS_NOP, cause,currentInputObject,currentInputState,(currentInputState == store->lastValue));
} }
else else
@@ -1064,7 +1072,7 @@ if (re)
res = changeState(IS_IDLE, cause,currentInputObject,currentInputState); res = changeState(IS_IDLE, cause,currentInputObject,currentInputState);
break; break;
default: default:
res = changeState(IS_NOP, cause,currentInputObject,currentInputState); res = changeState(IS_NOP, cause,currentInputObject,currentInputState, (currentInputState == store->lastValue));
} }
if (res) { //State changed or postponed if (res) { //State changed or postponed
// store->logicState = currentInputState; // store->logicState = currentInputState;
@@ -1186,13 +1194,14 @@ strncpy(addrstr,emit->valuestring,sizeof(addrstr));
if (mqttClient.connected() && !ethernetIdleCount) if (mqttClient.connected() && !ethernetIdleCount)
{ {
if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestring); if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestring);
if (newValue) { //send set command if (newValue) { //send set command
if (!scmd || scmd->type != aJson_String) mqttClient.publish(addrstr, "ON", true); if (!scmd) {mqttClient.publish(addrstr, "ON", true); debugSerial<<F("Emit:")<<addrstr<< F("->") << "ON"<<endl;}
else if (strlen(scmd->valuestring)) else if ((scmd->type == aJson_String) && strlen(scmd->valuestring))
mqttClient.publish(addrstr, scmd->valuestring, true); {mqttClient.publish(addrstr, scmd->valuestring, true);debugSerial<<F("Emit:")<<addrstr<< F("->") << scmd->valuestring<<endl;}
} else { //send reset command } else { //send reset command
if (!rcmd || rcmd->type != aJson_String) mqttClient.publish(addrstr, "OFF", true); if (!rcmd) {mqttClient.publish(addrstr, "OFF", true);debugSerial<<F("Emit:")<<addrstr<< F("->") << "OFF"<<endl;}
else if (strlen(rcmd->valuestring))mqttClient.publish(addrstr, rcmd->valuestring, true); else if ((rcmd->type == aJson_String) && strlen(rcmd->valuestring)) {mqttClient.publish(addrstr, rcmd->valuestring, true);debugSerial<<F("Emit:")<<addrstr<< F("->") << rcmd->valuestring<<endl;}
} }
} }
#endif //NOIP #endif //NOIP
@@ -1203,12 +1212,12 @@ if (!strchr(addrstr,'/')) setTopic(addrstr,sizeof(addrstr),T_OUT,emit->valuestri
Item it(item->valuestring); Item it(item->valuestring);
if (it.isValid()) { if (it.isValid()) {
if (newValue) { //send set command if (newValue) { //send set command
if (!scmd || scmd->type != aJson_String) it.Ctrl(itemCmd(ST_VOID,CMD_ON)); if (!scmd ) it.Ctrl(itemCmd(ST_VOID,CMD_ON));
else if (strlen(scmd->valuestring)) else if ((scmd->type == aJson_String) && strlen(scmd->valuestring))
it.Ctrl(scmd->valuestring); it.Ctrl(scmd->valuestring);
} else { //send reset command } else { //send reset command
if (!rcmd || rcmd->type != aJson_String) it.Ctrl(itemCmd(ST_VOID,CMD_OFF)); if (!rcmd ) it.Ctrl(itemCmd(ST_VOID,CMD_OFF));
else if (strlen(rcmd->valuestring)) else if ((rcmd->type == aJson_String) && strlen(rcmd->valuestring))
it.Ctrl(rcmd->valuestring); it.Ctrl(rcmd->valuestring);
} }
} }

View File

@@ -193,7 +193,7 @@ protected:
bool publishDataToDomoticz(int , aJsonObject *, const char *format, ...); bool publishDataToDomoticz(int , aJsonObject *, const char *format, ...);
char* getIdxField(); char* getIdxField();
bool changeState(uint8_t newState, short cause, aJsonObject * currentInputObject, bool contactState); bool changeState(uint8_t newState, short cause, aJsonObject * currentInputObject, bool contactState, bool calledOnTimer = false);
void setupRotaryEncoder(); void setupRotaryEncoder();
aJsonObject * getCurrentInput(); aJsonObject * getCurrentInput();

View File

@@ -60,8 +60,18 @@ const reg_t regSize_P[] PROGMEM =
} ; } ;
#define regSizeNum sizeof(regSize_P)/sizeof(reg_t) #define regSizeNum sizeof(regSize_P)/sizeof(reg_t)
/**
* @brief Меняет порядок байтов в 16-битном числе.
* @param x Входное число.
* @return Число с изменённым порядком байтов.
*/
uint16_t swap (uint16_t x) {return ((x & 0xff) << 8) | ((x & 0xff00) >> 8);} uint16_t swap (uint16_t x) {return ((x & 0xff) << 8) | ((x & 0xff00) >> 8);}
/**
* @brief Преобразует строку в тип регистра.
* @param str Строка с типом регистра.
* @return Код типа регистра.
*/
int str2regSize(char * str) int str2regSize(char * str)
{ {
for(uint8_t i=0; i<regSizeNum && str;i++) for(uint8_t i=0; i<regSizeNum && str;i++)
@@ -71,6 +81,12 @@ int str2regSize(char * str)
} }
//TODO irs etc //TODO irs etc
/**
* @brief Получает имя параметра по номеру регистра.
* @param parameters JSON-объект с параметрами.
* @param regnum Номер регистра.
* @return Имя параметра или NULL.
*/
char * getParamNameByReg(aJsonObject * parameters, int regnum) char * getParamNameByReg(aJsonObject * parameters, int regnum)
{ {
if (!parameters) return NULL; if (!parameters) return NULL;
@@ -89,6 +105,11 @@ char * getParamNameByReg(aJsonObject * parameters, int regnum)
return NULL; return NULL;
} }
/**
* @brief Проверяет наличие действия в JSON-объекте.
* @param execObj JSON-объект.
* @return true, если действие найдено, иначе false.
*/
bool haveAction(aJsonObject * execObj) bool haveAction(aJsonObject * execObj)
{ {
aJsonObject * j = execObj->child; aJsonObject * j = execObj->child;
@@ -111,6 +132,10 @@ case aJson_Array:
return false; return false;
} }
/**
* @brief Загружает и применяет конфигурацию Modbus.
* @return true, если конфигурация успешно загружена, иначе false.
*/
bool out_Modbus::getConfig() bool out_Modbus::getConfig()
{ {
// Retrieve and store template values from global modbus settings // Retrieve and store template values from global modbus settings
@@ -224,6 +249,11 @@ bool out_Modbus::getConfig()
} }
/**
* @brief Создаёт поле для хранения последнего измеренного значения по имени параметра.
* @param name Имя параметра.
* @return true, если успешно, иначе false.
*/
int out_Modbus::createLastMeasured(char * name) int out_Modbus::createLastMeasured(char * name)
{ {
if (!name) return false; if (!name) return false;
@@ -231,6 +261,11 @@ aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
return createLastMeasured(aJson.getObjectItem(itemParametersObj,name)); return createLastMeasured(aJson.getObjectItem(itemParametersObj,name));
} }
/**
* @brief Создаёт поле для хранения последнего измеренного значения по JSON-объекту.
* @param execObj JSON-объект параметра.
* @return true, если успешно, иначе false.
*/
int out_Modbus::createLastMeasured(aJsonObject * execObj) int out_Modbus::createLastMeasured(aJsonObject * execObj)
{ {
if (!execObj) return false; if (!execObj) return false;
@@ -255,6 +290,11 @@ int out_Modbus::createLastMeasured(aJsonObject * execObj)
return true; return true;
} }
/**
* @brief Получает объект последнего измеренного значения по имени параметра.
* @param name Имя параметра.
* @return JSON-объект или NULL.
*/
aJsonObject * out_Modbus::getLastMeasured(char * name) aJsonObject * out_Modbus::getLastMeasured(char * name)
{ {
if (!name) return NULL; if (!name) return NULL;
@@ -262,6 +302,11 @@ aJsonObject * itemParametersObj = aJson.getArrayItem(item->itemArg, 2);
return getLastMeasured (aJson.getObjectItem(itemParametersObj,name)); return getLastMeasured (aJson.getObjectItem(itemParametersObj,name));
} }
/**
* @brief Получает объект последнего измеренного значения по JSON-объекту.
* @param execObj JSON-объект параметра.
* @return JSON-объект или NULL.
*/
aJsonObject * out_Modbus::getLastMeasured(aJsonObject * execObj) aJsonObject * out_Modbus::getLastMeasured(aJsonObject * execObj)
{ {
if (!execObj) return NULL; if (!execObj) return NULL;
@@ -271,6 +316,10 @@ if (lastMeasured && lastMeasured->type == aJson_Int) return lastMeasured;
return NULL; return NULL;
} }
/**
* @brief Инициализирует канал Modbus и загружает конфигурацию.
* @return 1 при успехе, 0 при ошибке.
*/
int out_Modbus::Setup() int out_Modbus::Setup()
{ {
abstractOut::Setup(); abstractOut::Setup();
@@ -295,6 +344,10 @@ else
} }
/**
* @brief Останавливает работу канала Modbus и освобождает ресурсы.
* @return 1 при успехе.
*/
int out_Modbus::Stop() int out_Modbus::Stop()
{ {
debugSerial.print("MBUS: De-Init "); debugSerial.print("MBUS: De-Init ");
@@ -309,6 +362,13 @@ return 1;
/**
* @brief Читает данные из Modbus-устройства.
* @param reg Номер регистра.
* @param regType Тип регистра.
* @param count Количество регистров.
* @return true, если чтение успешно, иначе false.
*/
bool readModbus(uint16_t reg, int regType, int count) bool readModbus(uint16_t reg, int regType, int count)
{ {
uint8_t result; uint8_t result;
@@ -336,6 +396,17 @@ return (result == node.ku8MBSuccess);
/**
* @brief Находит и обрабатывает регистр Modbus, выполняет сопоставление и действия.
* @param registerNum Номер регистра.
* @param posInBuffer Позиция в буфере ответа.
* @param regType Тип регистра.
* @param registerFrom Начальный регистр диапазона.
* @param registerTo Конечный регистр диапазона.
* @param doExecution Выполнять ли действие.
* @param submitParam Флаг для подавления повторных действий.
* @return Команда itemCmd с результатом.
*/
itemCmd out_Modbus::findRegister(uint16_t registerNum, uint16_t posInBuffer, uint8_t regType, uint16_t registerFrom, uint16_t registerTo, bool doExecution, bool * submitParam) itemCmd out_Modbus::findRegister(uint16_t registerNum, uint16_t posInBuffer, uint8_t regType, uint16_t registerFrom, uint16_t registerTo, bool doExecution, bool * submitParam)
{ {
aJsonObject * paramObj = store->parameters->child; aJsonObject * paramObj = store->parameters->child;
@@ -627,7 +698,12 @@ return itemCmd();
} }
void out_Modbus::pollModbus(aJsonObject * reg, int regType) /**
* @brief Опрос Modbus-устройства по списку регистров.
* @param reg JSON-объект с регистрами.
* @param regType Тип регистра.
*/
void out_Modbus::pollModbus(aJsonObject * reg, int regType)
{ {
if (!reg) return; if (!reg) return;
reg=reg->child; reg=reg->child;
@@ -665,6 +741,9 @@ return itemCmd();
} }
} }
/**
* @brief Инициализирует линию связи Modbus.
*/
void out_Modbus::initLine() void out_Modbus::initLine()
{ {
//store->serialParam=(USARTClass::USARTModes) SERIAL_8N1; //store->serialParam=(USARTClass::USARTModes) SERIAL_8N1;
@@ -683,6 +762,12 @@ void out_Modbus::initLine()
node.begin(item->getArg(0), modbusSerial); node.begin(item->getArg(0), modbusSerial);
} }
/**
* @brief Отправляет значение в Modbus-устройство.
* @param paramName Имя параметра.
* @param outValue JSON-объект с отправляемым значением.
* @return 0 при успехе, отрицательное значение при ошибке.
*/
int out_Modbus::sendModbus(char * paramName, aJsonObject * outValue) int out_Modbus::sendModbus(char * paramName, aJsonObject * outValue)
{ {
if (!store) {errorSerial<<F(" internal send error - no store")<<endl; return -1;} if (!store) {errorSerial<<F(" internal send error - no store")<<endl; return -1;}
@@ -819,6 +904,11 @@ if ((res ==0) && (outValue->type == aJson_Int) && lastMeasured && (lastMeasured-
return ( res == 0); return ( res == 0);
} }
/**
* @brief Осуществляет опрос и отправку команд Modbus.
* @param cause Причина вызова (например, медленный опрос).
* @return Интервал следующего опроса.
*/
int out_Modbus::Poll(short cause) int out_Modbus::Poll(short cause)
{ {
if (cause==POLLING_SLOW) return 0; if (cause==POLLING_SLOW) return 0;
@@ -949,11 +1039,21 @@ if (store->pollingRegisters || store->pollingIrs || store->pollingCoils || store
return store->pollingInterval; return store->pollingInterval;
}; };
/**
* @brief Возвращает тип канала.
* @return CH_MBUS.
*/
int out_Modbus::getChanType() int out_Modbus::getChanType()
{ {
return CH_MBUS; return CH_MBUS;
} }
/**
* @brief Отправляет команду itemCmd в Modbus по шаблону параметра.
* @param templateParamObj JSON-объект шаблона параметра.
* @param cmd Команда itemCmd.
* @return 1 при успехе, 0 при ошибке.
*/
int out_Modbus::sendItemCmd(aJsonObject *templateParamObj, itemCmd cmd) int out_Modbus::sendItemCmd(aJsonObject *templateParamObj, itemCmd cmd)
{ {
if (templateParamObj) if (templateParamObj)
@@ -1050,6 +1150,14 @@ else return 0;
// 2. custom textual subItem // 2. custom textual subItem
// 3. non-standard numeric suffix Code equal param id // 3. non-standard numeric suffix Code equal param id
/**
* @brief Унифицированное управление Modbus-каналом.
* @param cmd Команда itemCmd.
* @param subItem Имя подэлемента.
* @param toExecute Выполнять ли команду.
* @param authorized Авторизовано ли выполнение.
* @return Результат выполнения.
*/
int out_Modbus::Ctrl(itemCmd cmd, char* subItem, bool toExecute,bool authorized) int out_Modbus::Ctrl(itemCmd cmd, char* subItem, bool toExecute,bool authorized)
{ {
if (!store) return -1; if (!store) return -1;