#include "esphome.h" using namespace esphome; #define LED_GLOBAL 0x3E // all LEDs #define LED_ON 0x02 #define LED_FADE 0x00 // no fade #define LED_BRIGHTNESS 0x00 // off #define DIR_PORT 13 #define BAUDRATE 9600 #define MAX_BYTES_PER_FRAME 16 #define MAX_PAYLOAD 3 uint8_t min_payload[3] = { LED_ON, LED_BRIGHTNESS, LED_FADE }; // MIN Protocol v2.0 Copyright (c) 2014-2017 JK Energy Ltd. // Use authorized under the MIT license. // Special protocol bytes enum { HEADER_BYTE = 0xaaU, STUFF_BYTE = 0x55U, EOF_BYTE = 0x55U, }; // Number of bytes needed for a frame with a given payload length, excluding stuff bytes // 3 header bytes, ID/control byte, length byte, seq byte, 4 byte CRC, EOF byte #define ON_WIRE_SIZE(p) ((p) + 11U) struct crc32_context { uint32_t crc; }; struct min_context { uint8_t rx_frame_payload_buf[MAX_PAYLOAD]; // Payload received so far uint32_t rx_frame_checksum; // Checksum received over the wire struct crc32_context rx_checksum; // Calculated checksum for receiving frame struct crc32_context tx_checksum; // Calculated checksum for sending frame uint8_t rx_header_bytes_seen; // Countdown of header bytes to reset state uint8_t rx_frame_state; // State of receiver uint8_t rx_frame_payload_bytes; // Length of payload received so far uint8_t rx_frame_id_control; // ID and control bit of frame being received uint8_t rx_frame_seq; // Sequence number of frame being received uint8_t rx_frame_length; // Length of frame uint8_t rx_control; // Control byte uint8_t tx_header_byte_countdown; // Count out the header bytes uint8_t port; // Number of the port associated with the context }; // MIN calback functions // --------------------------------------------------------------------------- void min_tx_start(uint8_t port) { digitalWrite(DIR_PORT, HIGH); // switch RS485 driver to send mode } // --------------------------------------------------------------------------- void min_tx_finished(uint8_t port) { Serial.flush(); digitalWrite(DIR_PORT, LOW); // switch RS485 driver to send mode } // --------------------------------------------------------------------------- // Tell MIN how much space there is to write to the serial port. This is used // inside MIN to decide whether to bother sending a frame or not. uint16_t min_tx_space(uint8_t port) { return MAX_BYTES_PER_FRAME; } // --------------------------------------------------------------------------- // Send a character on the designated port. void min_tx_byte(uint8_t port, uint8_t byte) { // Ignore 'port' because we have just one context. Serial.write(byte); } // --------------------------------------------------------------------------- static void crc32_init_context(struct crc32_context *context) { context->crc = 0xffffffffU; } // --------------------------------------------------------------------------- static void crc32_step(struct crc32_context *context, uint8_t byte) { uint32_t j; context->crc ^= byte; for(j = 0; j < 8; j++) { uint32_t mask = (uint32_t) -(context->crc & 1U); context->crc = (context->crc >> 1) ^ (0xedb88320U & mask); } } // --------------------------------------------------------------------------- static uint32_t crc32_finalize(struct crc32_context *context) { return ~context->crc; } // --------------------------------------------------------------------------- static void stuffed_tx_byte(struct min_context *self, uint8_t byte, bool crc) { // Transmit the byte min_tx_byte(self->port, byte); if(crc) { crc32_step(&self->tx_checksum, byte); } // See if an additional stuff byte is needed if(byte == HEADER_BYTE) { if(--self->tx_header_byte_countdown == 0) { min_tx_byte(self->port, STUFF_BYTE); // Stuff byte self->tx_header_byte_countdown = 2U; } } else { self->tx_header_byte_countdown = 2U; } } // --------------------------------------------------------------------------- // Send frame on wire static void on_wire_bytes(struct min_context *self, uint8_t id_control, uint8_t seq, uint8_t const *payload_base, uint16_t payload_offset, uint16_t payload_mask, uint8_t payload_len) { uint8_t n, i; uint32_t checksum; self->tx_header_byte_countdown = 2U; crc32_init_context(&self->tx_checksum); min_tx_start(self->port); // Header is 3 bytes; because unstuffed will reset receiver immediately min_tx_byte(self->port, HEADER_BYTE); min_tx_byte(self->port, HEADER_BYTE); min_tx_byte(self->port, HEADER_BYTE); stuffed_tx_byte(self, id_control, true); if(id_control & 0x80U) { // Send the sequence number if it is a transport frame stuffed_tx_byte(self, seq, true); } stuffed_tx_byte(self, payload_len, true); for(i = 0, n = payload_len; n > 0; n--, i++) { stuffed_tx_byte(self, payload_base[payload_offset], true); payload_offset++; payload_offset &= payload_mask; } checksum = crc32_finalize(&self->tx_checksum); // Network order is big-endian. A decent C compiler will spot that this // is extracting bytes and will use efficient instructions. stuffed_tx_byte(self, (uint8_t)((checksum >> 24) & 0xffU), false); stuffed_tx_byte(self, (uint8_t)((checksum >> 16) & 0xffU), false); stuffed_tx_byte(self, (uint8_t)((checksum >> 8) & 0xffU), false); stuffed_tx_byte(self, (uint8_t)((checksum >> 0) & 0xffU), false); // Ensure end-of-frame doesn't contain 0xaa and confuse search for start-of-frame min_tx_byte(self->port, EOF_BYTE); min_tx_finished(self->port); } // --------------------------------------------------------------------------- // Sends an application MIN frame on the wire (do not put into the transport queue) void min_send_frame(struct min_context *self, uint8_t min_id, uint8_t const *payload, uint8_t payload_len) { if((ON_WIRE_SIZE(payload_len) <= min_tx_space(self->port))) { on_wire_bytes(self, min_id & (uint8_t) 0x3fU, 0, payload, 0, 0xffffU, payload_len); } } struct min_context min_ctx; // min protocol context // --------------------------------------------------------------------------- // float output class class Min_Led : public Component, public FloatOutput { public: void setup() override { // switch RS485 driver to receive mode digitalWrite(DIR_PORT, LOW); Serial.begin(BAUDRATE); } void write_state(float state) override { // state is the amount this output should be on, from 0.0 to 1.0 // we need to convert it to an integer first int value = state * 255; min_payload[1] = value; min_send_frame(&min_ctx, LED_GLOBAL, min_payload, MAX_PAYLOAD); } };