Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SKARSTA: Adding functionality to control the table with serial communication #137

Merged
merged 2 commits into from
Aug 15, 2021

Conversation

p0thi
Copy link
Contributor

@p0thi p0thi commented May 4, 2021

I created a new Service called SerialCom that adds the functionality to control the table with serial communication.

It also sends the current position of the table (in a special format [see below]) to the serial connection every time it changes, but at a maximum rate of every 500ms.
The SERIAL_TX_PIN and SERIAL_RX_PIN pins can be changed in the configuration.h file (default RX = 12; default TX = 11)
The baud rate of the serial connection is 9600.

Serial Input

Every command to the table has to be a new line. So in my case I add \r\n to every command string I send to the table.
The first character of the command signals the type of command.

It accepts the following commands:

  • %<number>: (e.g. %75)
    This sets the table position to <number> percent. The value is cramped between 0 and 100. The number can be a float.
  • #<number>: (e.g. #2)
    Move the table to the <number> preset. Accepted integers are 1, 2 and 3.
  • ?:
    This requests the service to send the current table position over the serial connection without changing it.
  • s:
    This stops the motor.
  • u:
    This starts the motor to spin cw until stopped.
  • d:
    This starts the motor to spin ccw until stopped.

Serial Output

The output to the serial connection of the table is always the current position. It will send every time the table position changes but twice per second at most.
It sends it in the following format:
<number1>/<number2> (e.g. 782/2187). <number1> is the current position and <number2> is the maximum range of the table (end_stop[0] - end_stop[1]).

My Use Case

I use this serial communication to control my table with a Wemos D1 mini chip over WiFi. This chip uses ESPHome and is integrated in my personal Home Assistant installation.
My skarsta.yaml config looks like follows:

esphome:
  name: skarsta
  platform: ESP8266
  board: d1_mini
  includes:
    - uart_read_line_sensor.h

wifi:
  ssid: <SSID>
  password: <PASSWORD>

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Skarsta Fallback Hotspot"
    password: "supersecretpassword"

captive_portal:

# Enable logging
logger:
  baud_rate: 9600

# Enable Home Assistant API
api:
  password: <API_PASSWORD>

ota:
  password: <OTA_PASSWORD>
  
uart:
  id: uart_bus
  tx_pin: D0
  rx_pin: D5
  baud_rate: 9600

switch:
  - platform: uart
    name: "Skarsta Preset 1"
    data: "#1\r\n"
  - platform: uart
    name: "Skarsta Preset 2"
    data: "#2\r\n"
  - platform: uart
    name: "Skarsta Preset 3"
    data: "#3\r\n"

text_sensor:
- platform: custom
  lambda: |-
    auto my_custom_sensor = new UartReadLineSensor(id(uart_bus));
    App.register_component(my_custom_sensor);
    return {my_custom_sensor};
  text_sensors:
    id: "uart_readline"
    
cover:
  - platform: template
    name: "Skarsta Manuell"
    lambda: |-
      String str = String(id(uart_readline).state.c_str());
      int index = str.indexOf('/');
      String first = str.substring(0, index);
      String second = str.substring(index + 1);
      return first.toFloat() / second.toFloat();
    has_position: true
    open_action:
      - uart.write: "u\r\n"
    close_action:
      - uart.write: "d\r\n"
    stop_action:
      - uart.write: "s\r\n"
    position_action:
      - uart.write: !lambda
          int percent = round(pos * 100);

          char numstr[21];
          std::string s = "%" + std::string(itoa(percent, numstr, 10)) + std::string("\r\n");

          std::vector<uint8_t> myVector(s.begin(), s.end());
          return myVector;

The UartReadLineSensor I use in my skarsta.yaml is in a file called uart_read_line_sensor.h in my esphome directory and has the follwin contents:

#include "esphome.h"

class UartReadLineSensor : public Component, public UARTDevice, public TextSensor {
 public:
  UartReadLineSensor(UARTComponent *parent) : UARTDevice(parent) {}

  void setup() override {
    publish_state("0/100");

    std::string s = "\r\n?\r\n";
    std::vector<uint8_t> myVector(s.begin(), s.end());

    for(const auto& value: myVector) {
      write(value);
    }
  }

  int readline(int readch, char *buffer, int len)
  {
    static int pos = 0;
    int rpos;

    if (readch > 0) {
      switch (readch) {
        case '\n': // Ignore new-lines
          break;
        case '\r': // Return on CR
          rpos = pos;
          pos = 0;  // Reset position index ready for next time
          return rpos;
        default:
          if (pos < len-1) {
            buffer[pos++] = readch;
            buffer[pos] = 0;
          }
      }
    }
    // No end of line has been found, so return -1.
    return -1;
  }

  void loop() override {
    const int max_line_length = 80;
    static char buffer[max_line_length];
    if (available() && readline(read(), buffer, max_line_length) > 0) {
      publish_state(buffer);
    }
  }
};

p0thi added 2 commits May 4, 2021 12:56
Merge changes of serial communication with current commit
@p0thi p0thi mentioned this pull request May 4, 2021
@RikMuilwijk
Copy link

Great contibution thanks! After uploading my code to the D1 mini, I can see the controls appear in Home Assistant. I'm only stuggling with the pinout of both the D1 and the Arduino Nano. I have not changed anything on the code.

Right now I tried to connect the TX/RX pins from the Arduino to the D0/D5 pins of the Wemos. The wemos is powered through the Nano's 3v and GND pins.

@p0thi
Copy link
Contributor Author

p0thi commented May 6, 2021

Great contibution thanks! After uploading my code to the D1 mini, I can see the controls appear in Home Assistant. I'm only stuggling with the pinout of both the D1 and the Arduino Nano. I have not changed anything on the code.

Right now I tried to connect the TX/RX pins from the Arduino to the D0/D5 pins of the Wemos. The wemos is powered through the Nano's 3v and GND pins.

Have you uploaded the code of this PR (link) to the nano? It is not yet merged into the master branch.
And have you made sure, that the TX pin of the D1 mini is connected to the RX pin of the nano and vice versa? I personally power my D1 mini using 5v to the 5v pin directly from the step down module, like the nano. But I think that should not be a problem.
And have you changed the WiFi setings etc in the yaml file?
Can you connect to and monitor the D1 mini after uploading the code?

@RikMuilwijk
Copy link

RikMuilwijk commented May 6, 2021

Great contibution thanks! After uploading my code to the D1 mini, I can see the controls appear in Home Assistant. I'm only stuggling with the pinout of both the D1 and the Arduino Nano. I have not changed anything on the code.
Right now I tried to connect the TX/RX pins from the Arduino to the D0/D5 pins of the Wemos. The wemos is powered through the Nano's 3v and GND pins.

Have you uploaded the code of this PR (link) to the nano? It is not yet merged into the master branch.
And have you made sure, that the TX pin of the D1 mini is connected to the RX pin of the nano and vice versa? I personally power my D1 mini using 5v to the 5v pin directly from the step down module, like the nano. But I think that should not be a problem.
And have you changed the WiFi setings etc in the yaml file?
Can you connect to and monitor the D1 mini after uploading the code?

Yup, I have checked the changes before uploading the code to the Nano. On the Home assistant end, I can read the Wemos output:

INFO Reading configuration /config/esphome/skarsta.yaml...
INFO Starting log output from 192.168.2.56 using esphome API
INFO Connecting to 192.168.2.56:6053 (192.168.2.56)
INFO Successfully connected to 192.168.2.56
[19:48:09][I][app:105]: ESPHome version 1.16.2 compiled on May  5 2021, 14:47:10
[19:48:09][C][wifi:443]: WiFi:
[19:48:09][C][wifi:303]:   SSID: [redacted]
[19:48:09][C][wifi:304]:   IP Address: 192.168.2.56
[19:48:09][C][wifi:306]:   BSSID: [redacted]
[19:48:09][C][wifi:307]:   Hostname: 'skarsta'
[19:48:09][C][wifi:311]:   Signal strength: -63 dB ▂▄▆█
[19:48:09][C][wifi:315]:   Channel: 6
[19:48:09][C][wifi:316]:   Subnet: 255.255.255.0
[19:48:09][C][wifi:317]:   Gateway: 192.168.2.254
[19:48:09][C][wifi:318]:   DNS1: (IP unset)
[19:48:09][C][wifi:319]:   DNS2: (IP unset)
[19:48:09][C][uart_esp8266:075]: UART Bus:
[19:48:09][C][uart_esp8266:077]:   TX Pin: GPIO16
[19:48:09][C][uart_esp8266:080]:   RX Pin: GPIO14
[19:48:09][C][uart_esp8266:081]:   RX Buffer Size: 256
[19:48:09][C][uart_esp8266:083]:   Baud Rate: 9600 baud
[19:48:09][C][uart_esp8266:084]:   Data Bits: 8
[19:48:09][C][uart_esp8266:085]:   Parity: NONE
[19:48:09][C][uart_esp8266:086]:   Stop bits: 1
[19:48:09][C][uart_esp8266:090]:   Using software serial
[19:48:09][C][template.cover:071]: Template Cover 'Skarsta Manuell'
[19:48:09][C][logger:185]: Logger:
[19:48:09][C][logger:186]:   Level: DEBUG
[19:48:09][C][logger:187]:   Log Baud Rate: 9600
[19:48:09][C][logger:188]:   Hardware UART: UART0
[19:48:09][C][uart.switch:020]: UART Switch 'Skarsta Preset 1'
[19:48:09][C][uart.switch:020]: UART Switch 'Skarsta Preset 2'
[19:48:09][C][uart.switch:020]: UART Switch 'Skarsta Preset 3'
[19:48:09][C][captive_portal:169]: Captive Portal:
[19:48:09][C][ota:029]: Over-The-Air Updates:
[19:48:09][C][ota:030]:   Address: 192.168.2.56:8266
[19:48:09][C][ota:032]:   Using Password.
[19:48:09][C][api:095]: API Server:
[19:48:09][C][api:096]:   Address: 192.168.2.56:6053
[19:48:17][D][switch:021]: 'Skarsta Preset 1' Turning ON.
[19:48:17][D][switch:045]: 'Skarsta Preset 1': Sending state ON
[19:48:17][D][uart.switch:016]: 'Skarsta Preset 1': Sending data...
[19:48:17][D][switch:045]: 'Skarsta Preset 1': Sending state OFF
[19:48:23][D][switch:021]: 'Skarsta Preset 2' Turning ON.
[19:48:23][D][switch:045]: 'Skarsta Preset 2': Sending state ON
[19:48:23][D][uart.switch:016]: 'Skarsta Preset 2': Sending data...
[19:48:23][D][switch:045]: 'Skarsta Preset 2': Sending state OFF
[19:48:24][D][switch:021]: 'Skarsta Preset 3' Turning ON.
[19:48:24][D][switch:045]: 'Skarsta Preset 3': Sending state ON
[19:48:24][D][uart.switch:016]: 'Skarsta Preset 3': Sending data...
[19:48:24][D][switch:045]: 'Skarsta Preset 3': Sending state OFF
[19:48:29][D][cover:072]: 'Skarsta Manuell' - Setting
[19:48:29][D][cover:080]:   Position: 39%
[19:48:29][D][cover:152]: 'Skarsta Manuell' - Publishing:
[19:48:29][D][cover:155]:   Position: 0%
[19:48:29][D][cover:168]:   Current Operation: IDLE

Commands I'm giving in the frontend are getting through but no reaction of the motor.

For pinout, I have connected the TX of the Wemos to the RX of the nano and vice versa like you mentioned. Could I read any logs on the Nano to see if commands from the Wemos are getting through?

edit; After looking at this log, it does seem that I would need to hook up TX/RX of the Nano to GPIO16 and 14 of the Wemos? I tried this, but that doesn't seem to work either..

edit2; I'm also abit confused about TX/RX being 11/12 by default. On pinout pictures of the Nano I see TX/RX being labeled as PD0 and PD1. But Arduino is a bit new to me as well, I'm sure I'm missing something obvious.

edit3; Whenever I push a switch in HomeAssistant I see the red RX led lighting up on the Nano. TX stays off, also when I control the motor manually.

@p0thi
Copy link
Contributor Author

p0thi commented May 7, 2021

There is a difference between the hardware serial communication (the one you use to see the outputs via USB) and a software serial communication. I am only using a software serial communication, because for that the pins can be changed and it is independent of the hardware serial com part. That is why I use the pins D12 and D11 on the nano and D0 and D5 on the Wemos.

The RX light on the nano shouldn't light up, since it is only lighting up for the hardware serial com.

The orange cable on the D11 pin on the nano is connected to the D5 pin on the D1 mini (a bit hard to see)
and the yellow cable on the D12 pin on the nano is connected to the D0 pin on the D1 mini.

Arduino Nano D1 mini
nano d1_mini

When you move the table by pressing a button you should also see something like follows in the ESPHome output.

[13:52:20][D][text_sensor:015]: 'uart_readline': Sending state '50/2188'
[13:52:20][D][cover:152]: 'Skarsta Manuell' - Publishing:
[13:52:20][D][cover:155]:   Position: 2%
[13:52:20][D][cover:168]:   Current Operation: IDLE
[13:52:21][D][text_sensor:015]: 'uart_readline': Sending state '75/2188'
[13:52:21][D][cover:152]: 'Skarsta Manuell' - Publishing:
[13:52:21][D][cover:155]:   Position: 3%
[13:52:21][D][cover:168]:   Current Operation: IDLE

The only things I changed in the code for the nano are some definition tweaks:
in the platformio.ini I changed

default_envs =
    nanoatmega328-relays
    nanoatmega328-urelays
    nanoatmega328-irelays
    nanoatmega328-iurelays
    nanoatmega328-bridge
    nanoatmega328-ubridge

to

default_envs =
    ; nanoatmega328-relays
    ; nanoatmega328-urelays
    ; nanoatmega328-irelays
    ; nanoatmega328-iurelays
    nanoatmega328-bridge
    ; nanoatmega328-ubridge

In the MotorBridge.h i changed

#define MIN_SPEED 150

to

#define MIN_SPEED 175

but I don't know how that would impact the functionality.

Do you have everything setup like I did?

@RikMuilwijk
Copy link

RikMuilwijk commented May 8, 2021

There is a difference between the hardware serial communication (the one you use to see the outputs via USB) and a software serial communication. I am only using a software serial communication, because for that the pins can be changed and it is independent of the hardware serial com part. That is why I use the pins D12 and D11 on the nano and D0 and D5 on the Wemos.

The RX light on the nano shouldn't light up, since it is only lighting up for the hardware serial com.

The orange cable on the D11 pin on the nano is connected to the D5 pin on the D1 mini (a bit hard to see)
and the yellow cable on the D12 pin on the nano is connected to the D0 pin on the D1 mini.

...

Do you have everything setup like I did?

That explains alot, thanks! All is working as intended. Really nifty future on the percentage scale in Home Assistant, makes automation even easier. I'm thinking of an automation that reminds you to stand up/sit down every now and then based on occupancy.

@p0thi
Copy link
Contributor Author

p0thi commented May 9, 2021

There is a difference between the hardware serial communication (the one you use to see the outputs via USB) and a software serial communication. I am only using a software serial communication, because for that the pins can be changed and it is independent of the hardware serial com part. That is why I use the pins D12 and D11 on the nano and D0 and D5 on the Wemos.
The RX light on the nano shouldn't light up, since it is only lighting up for the hardware serial com.
The orange cable on the D11 pin on the nano is connected to the D5 pin on the D1 mini (a bit hard to see)
and the yellow cable on the D12 pin on the nano is connected to the D0 pin on the D1 mini.
...
Do you have everything setup like I did?

That explains alot, thanks! All is working as intended. Really nifty future on the percentage scale in Home Assistant, makes automation even easier. I'm thinking of an automation that reminds you to stand up/sit down every now and then based on occupancy.

Great! I'm glad you like it. :)

@aenniw aenniw self-requested a review July 7, 2021 16:55
@aenniw aenniw added the enhancement New feature or request label Jul 7, 2021
Copy link
Owner

@aenniw aenniw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Could you summarize the setup procedure that is mentioned in this PR into skarsta/README.md or skarsta/docs/ with examples? Also I'm curious about the use case with EspHome do you have pre-scheduled times when the table moves?

It seems that some parts of code have inconsistent spacing, could you reformat it?

if (NULL != mySerial) {
delete mySerial;
}
mySerial = new SoftwareSerial(ixPin, txPin);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be moved to to constructor like so?

SerialCom::SerialCom(Motor *motor, Calibrator *calibrator, uint8_t rx, uint8_t tx) :
        motor(motor), calibrator(calibrator), mySerial(rx, tx) {
}

return true;
}

void SerialCom::disable() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be dropped


class SerialCom : public Service {
private:
bool disabled = false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be dropped


#include <SoftwareSerial.h>

const byte ixPin = 11;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ixPin txPin not used anywhere can be dropped

@@ -41,8 +42,9 @@ MotorRelay motor(SENSOR_PIN0, SENSOR_PIN1, POWER_RELAY, DIRECTION_RELAY, STOP_PO
Display display(DISPLAY_PIN_CLK, DISPLAY_PIN_DIO, FADE_TIMEOUT);
Watchdog watchdog(&motor, WATCHDOG_TIMEOUT, WATCHDOG_DEADLOCK_CHANGE, WATCHDOG_OTHER_CHANGE, WATCHDOG_OTHER_SLEEP);
Calibrator calibrator(&motor);
SerialCom serialCom(&motor, &calibrator, SERIAL_RX_PIN, SERIAL_TX_PIN);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be optional feature enabled only via __ESPHOME__

{
float percent = min(100, max(0, line.toFloat())) / 100;
int position = round(motor->get_end_stop_low() + (motor->get_end_stop_high() - motor->get_end_stop_low()) * percent);
if (motor->get_mode() != CALIBRATED)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check can be dropped as its redundant with

void Motor::set_position(unsigned int pos) {
    if (mode != CALIBRATED || position_abs(pos, get_position()) < min_change) {
        return;
    }

set_motor(position); can be simplified to

motor->set_position(position);

}

void SerialCom::goto_preset(unsigned int preset) {
if (motor->get_mode() != CALIBRATED)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check can be dropped as its redundant with

void Motor::set_position(unsigned int pos) {
    if (mode != CALIBRATED || position_abs(pos, get_position()) < min_change) {
        return;
    }

case 3:
{
unsigned int preset = calibrator->get_preset(number - 1);
goto_preset(preset);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified

motor->set_position(calibrator->get_preset(number - 1));

private:
bool disabled = false;
unsigned int pos_watch = 0;
unsigned long last_update = 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be replaced with TimedService see Watchdog.cpp for example

@@ -94,6 +94,10 @@ class Motor : public TimedService {

MotorMode get_mode() const;

unsigned int get_end_stop_low();
unsigned int get_end_stop_high();
long get_next_position();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be dropped as not needed

@aenniw aenniw merged commit f51433a into aenniw:master Aug 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants