someweisguy / esp_dmx

Espressif ESP32 implementation of ANSI-ESTA E1.11 DMX-512A and E1.20 RDM
MIT License
307 stars 30 forks source link

Add RDM queue #102

Closed someweisguy closed 8 months ago

someweisguy commented 9 months ago

This PR adds the RDM queue feature of RDM. It allows the RDM driver to save queued messages for retrieval by RDM controllers.

Support of queued messages are important to allow for better communication between the RDM responder and the RDM controller. If, for example, a technician manually changes the DMX address of an RDM responder without sending a SET RDM_PID_DMX_START_ADDRESS request (i.e., the technician uses the RDM responder's DIP switches), the RDM controller will have no way to know that the responder's DMX address was updated.

With RDM queued messages, when the RDM responder's DMX start address is manually updated a RDM message is placed in the responder's message queue. The RDM controller can see that the responder queued a message. The controller will read the queued message and see that the DMX start address was updated.

This PR is created as a draft to allow for discussion.

someweisguy commented 9 months ago

I believe the implementation for this feature can be somewhat simple. An uint16_t array is needed to store PIDs which have been updated. When a RDM_PID_QUEUED_MESSAGE request is received, the driver can fetch the current value of the PID (using rdm_pd_find() perhaps?) and copy that value into a packet to be sent to the RDM controller.

The uint16_t array may need to be a deque to allow random insertion and deletion of elements in a FIFO manner.

An additional uint16_t will be used to store the last sent queued message. I am not positive what the behavior of the "last sent queued message" feature is supposed to be, exactly. Using the implementation described above, there is potential for the last sent queued message PID to have been updated by the user between RDM controller requests for the last sent queued message. Therefore, the last queued sent message may differ between subsequent requests. I am not sure if the last sent queued message should always be the same.

There may be additional edge-cases that I have not considered yet.

someweisguy commented 9 months ago

This PR also updates the behavior of some RDM utilities, including the behavior for how parameters are flagged to be saved in NVS. When calling rdm_register_parameter() a flag (bool nvs) has been added to the signature which is stored within the DMX driver. This flag indicates to the driver that the parameter is non-volatile and must persist after power cycle. This will allow users to add custom RDM parameters which persist after reboot and should improve code readability in other places.

An add_to_queue flag has been added to rdm_set_parameter(). When set to true, this will add the parameter which is being set to the RDM queue. This value should be true whenever a parameter is being set as the result of physically interfacing with the ESP32, such as setting dip switches. The flag should be set to false when a parameter is being set as a result of responding to an RDM message.

someweisguy commented 8 months ago

I've roughed in the RDM_PID_QUEUED_MESSAGE response. In order to make testing more efficient, I'm switching gears to implement a rdm_send_get_queued_messages() function. This function is going to be unique amongst the other rdm_send_get_ functions. Its uniqueness is due to the fact that a queued message request can receive a response from almost any PID. In other words, the RDM controller can send a RDM_PID_QUEUED_MESSAGE request and receive a RDM_PID_DMX_START_ADDRESS response. Because of this I am adding a .pid member to rdm_ack_t so that users are able to read back the PID of the response packet.

I am considering what the signature of the rdm_send_get_queued_messages() function should look like. Because it can receive any PID as a response, handling response data may be difficult. If a known PID response is received it should be trivial to lookup the PID param_str to emplace the data into a buffer, but it is possible that a unknown PID is received which means it would be impossible to emplace data. In other words, it would be up to the user to emplace their own data from queued message responses. Example code may look like the below:

rdm_header_t header = {
  .dest_uid = 0xabcd12345678
};
rdm_status_type_t status_type = RDM_STATUS_TYPE_ERROR;
uint8_t pd[231];
rdm_send_get_queued_messages(dmx_num, &header, status_type, pd, &ack);

if (!ack.err && ack.type == RDM_RESPONSE_TYPE_ACK) {
  if (ack.pid == RDM_PID_DMX_START_ADDRESS) {
    uint16_t dmx_start_address;
    rdm_pd_emplace(&dmx_start_address, "w$", pd, 2, true);
    printf("New DMX start address is %i\n", dmx_start_address); 
  }
}

I have been toying with this idea of adding a function which takes a PID as an input and returns a param_str as an output. This would likely reduce code size somewhat. This function would be helpful in this case for cleaning up the handling of these queued messages responses:

if (!ack.err && ack.type == RDM_RESPONSE_TYPE_ACK) {
  const char *param_str = rdm_get_param_str(ack.pid);
  rdm_pd_emplace(pd, param_str, pd, ack.size, true);
}
// The pd array can now be reinterpret casted to the appropriate type and be used.

This probably won't be exactly how I wind up implementing this feature, but I think this is likely the best way forward unless anyone reading this has thoughts!