2 Angajamente c56a318bbb ... 07cee55b97

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  Scott Bezek 07cee55b97 WIP protobuf protocol 4 ani în urmă
  Scott Bezek 812449da04 Log state changes 4 ani în urmă

+ 2 - 2
firmware/src/interface_task.cpp

@@ -120,7 +120,7 @@ static PB_SmartKnobConfig configs[] = {
 };
 
 InterfaceTask::InterfaceTask(const uint8_t task_core, MotorTask& motor_task, DisplayTask* display_task) : 
-        Task("Interface", 2048, 1, task_core),
+        Task("Interface", 3000, 1, task_core),
         stream_(),
         motor_task_(motor_task),
         display_task_(display_task),
@@ -201,7 +201,7 @@ void InterfaceTask::run() {
 
         std::string* log_string;
         while (xQueueReceive(log_queue_, &log_string, 0) == pdTRUE) {
-            stream_.println(log_string->c_str());
+            current_protocol->log(log_string->c_str());
             delete log_string;
         }
 

+ 8 - 0
firmware/src/serial/serial_protocol_plaintext.cpp

@@ -3,7 +3,15 @@
 #include "serial_protocol_plaintext.h"
 
 void SerialProtocolPlaintext::handleState(const PB_SmartKnobState& state) {
+    bool substantial_change = (latest_state_.current_position != state.current_position)
+        || (latest_state_.config.detent_strength_unit != state.config.detent_strength_unit)
+        || (latest_state_.config.endstop_strength_unit != state.config.endstop_strength_unit)
+        || (latest_state_.config.num_positions != state.config.num_positions);
     latest_state_ = state;
+
+    if (substantial_change) {
+        stream_.printf("STATE: %d/%d  (detent strength: %0.2f, width: %0.0f deg, endstop strength: %0.2f)\n", state.current_position, state.config.num_positions - 1, state.config.detent_strength_unit, degrees(state.config.position_width_radians), state.config.endstop_strength_unit);
+    }
 }
 
 void SerialProtocolPlaintext::log(const char* msg) {

+ 134 - 1
firmware/src/serial/serial_protocol_protobuf.cpp

@@ -1,15 +1,148 @@
 #include "../proto_gen/smartknob.pb.h"
 
+#include "crc32.h"
+#include "PacketSerial.h"
+#include "pb_encode.h"
+#include "pb_decode.h"
 #include "serial_protocol_protobuf.h"
 
+static SerialProtocolProtobuf* singleton_for_packet_serial = 0;
+
+static const uint16_t MIN_STATE_INTERVAL_MILLIS = 250;
+static const uint16_t PERIODIC_STATE_INTERVAL_MILLIS = 5000;
+
+SerialProtocolProtobuf::SerialProtocolProtobuf(Stream& stream) :
+        SerialProtocol(),
+        stream_(stream) {
+    packet_serial_.setStream(&stream);
+
+    // Note: not threadsafe or instance safe!! but PacketSerial requires a legacy function pointer, so we can't
+    // use a member, std::function, or lambda with captures
+    assert(singleton_for_packet_serial == 0);
+    singleton_for_packet_serial = this;
+
+    packet_serial_.setPacketHandler([](const uint8_t* buffer, size_t size) {
+        singleton_for_packet_serial->handlePacket(buffer, size);
+    });
+}
+
 void SerialProtocolProtobuf::handleState(const PB_SmartKnobState& state) {
+    latest_state_ = state;
+}
+
+void SerialProtocolProtobuf::ack(uint32_t nonce) {
+    pb_tx_buffer_ = {};
+    pb_tx_buffer_.which_payload = PB_FromSmartKnob_ack_tag;
+    pb_tx_buffer_.payload.ack.nonce = nonce;
+    sendPbTxBuffer();
 }
 
 void SerialProtocolProtobuf::log(const char* msg) {
+    pb_tx_buffer_ = {};
+    pb_tx_buffer_.which_payload = PB_FromSmartKnob_log_tag;
+
+    strlcpy(pb_tx_buffer_.payload.log.msg, msg, sizeof(pb_tx_buffer_.payload.log.msg));
+
+    sendPbTxBuffer();
 }
 
 void SerialProtocolProtobuf::loop() {
+    do {
+        packet_serial_.update();
+    } while (stream_.available());
+
+    // Rate limit state change transmissions
+    bool state_changed = latest_state_ != last_sent_state_ && millis() - last_sent_state_millis_ >= MIN_STATE_INTERVAL_MILLIS;
+
+    // Send state periodically or when forced, regardless of rate limit for state changes
+    bool force_send_state = state_requested_ || millis() - last_sent_state_millis_ > PERIODIC_STATE_INTERVAL_MILLIS;
+    if (state_changed || force_send_state) {
+        state_requested_ = false;
+        pb_tx_buffer_ = {};
+        pb_tx_buffer_.which_payload = PB_FromSmartKnob_smartknob_state_tag;
+        pb_tx_buffer_.payload.smartknob_state = latest_state_;
+
+        sendPbTxBuffer();
+
+        last_sent_state_ = latest_state_;
+        last_sent_state_millis_ = millis();
+    }
 }
 
-void SerialProtocolProtobuf::init() {
+void SerialProtocolProtobuf::handlePacket(const uint8_t* buffer, size_t size) {
+    if (size <= 4) {
+        // Too small, ignore bad packet
+        log("Small packet");
+        return;
+    }
+
+    // Compute and append little-endian CRC32
+    uint32_t expected_crc = 0;
+    crc32(buffer, size - 4, &expected_crc);
+
+    uint32_t provided_crc = buffer[size - 4]
+                         | (buffer[size - 3] << 8)
+                         | (buffer[size - 2] << 16)
+                         | (buffer[size - 1] << 24);
+
+    if (expected_crc != provided_crc) {
+        char buf[200];
+        snprintf(buf, sizeof(buf), "Bad CRC (%u byte packet). Expected %08x but got %08x.", size - 4, expected_crc, provided_crc);
+        log(buf);
+        return;
+    }
+
+    pb_istream_t stream = pb_istream_from_buffer(buffer, size - 4);
+    if (!pb_decode(&stream, PB_ToSmartknob_fields, &pb_rx_buffer_)) {
+        char buf[200];
+        snprintf(buf, sizeof(buf), "Decoding failed: %s", PB_GET_ERROR(&stream));
+        log(buf);
+        return;
+    }
+
+    // Always ACK immediately
+    ack(pb_rx_buffer_.nonce);
+    if (pb_rx_buffer_.nonce == last_nonce_) {
+        // Ignore any extraneous retries
+        char buf[200];
+        snprintf(buf, sizeof(buf), "Already handled nonce %u", pb_rx_buffer_.nonce);
+        log(buf);
+        return;
+    }
+    last_nonce_ = pb_rx_buffer_.nonce;
+    
+    switch (pb_rx_buffer_.which_payload) {
+        case PB_ToSmartknob_smartknob_config_tag: {
+            PB_SmartKnobConfig config = pb_rx_buffer_.payload.smartknob_config;
+            // TODO: do something with this
+            break;
+        }
+        default: {
+            char buf[200];
+            snprintf(buf, sizeof(buf), "Unknown payload type: %d", pb_rx_buffer_.which_payload);
+            log(buf);
+            return;
+        }
+    }
+}
+
+void SerialProtocolProtobuf::sendPbTxBuffer() {
+    // Encode protobuf message to byte buffer
+    pb_ostream_t stream = pb_ostream_from_buffer(tx_buffer_, sizeof(tx_buffer_));
+    if (!pb_encode(&stream, PB_FromSmartKnob_fields, &pb_tx_buffer_)) {
+        stream_.println(stream.errmsg);
+        stream_.flush();
+        assert(false);
+    }
+
+    // Compute and append little-endian CRC32
+    uint32_t crc = 0;
+    crc32(tx_buffer_, stream.bytes_written, &crc);
+    tx_buffer_[stream.bytes_written + 0] = (crc >> 0)  & 0xFF;
+    tx_buffer_[stream.bytes_written + 1] = (crc >> 8)  & 0xFF;
+    tx_buffer_[stream.bytes_written + 2] = (crc >> 16) & 0xFF;
+    tx_buffer_[stream.bytes_written + 3] = (crc >> 24) & 0xFF;
+
+    // Encode and send proto+CRC as a COBS packet
+    packet_serial_.send(tx_buffer_, stream.bytes_written + 4);
 }

+ 18 - 0
firmware/src/serial/serial_protocol_protobuf.h

@@ -17,4 +17,22 @@ class SerialProtocolProtobuf : public SerialProtocol {
     
     private:
         Stream& stream_;
+        PB_FromSmartKnob pb_tx_buffer_;
+        PB_ToSmartknob pb_rx_buffer_;
+
+        uint8_t tx_buffer_[PB_FromSmartKnob_size + 4]; // Max message size + CRC32
+
+        PacketSerial_<COBS, 0, (PB_ToSmartknob_size + 4) * 2 + 10> packet_serial_;
+
+        uint32_t last_nonce_;
+
+        PB_SmartKnobState latest_state_ = {};
+        PB_SmartKnobState last_sent_state_ = {};
+        uint32_t last_sent_state_millis_ = 0;
+
+        bool state_requested_;
+
+        void sendPbTxBuffer();
+        void handlePacket(const uint8_t* buffer, size_t size);
+        void ack(uint32_t nonce);
 };