├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .vscode
├── arduino.json
├── c_cpp_properties.json
└── settings.json
├── README.md
├── WiFiConfig.h.sample
├── alzaergo.ino
├── azure-pipelines-1.yml
├── azure-pipelines.yml
├── docs
└── control_panel_manual.pdf
├── images
├── control_box.png
├── control_box_pins.png
├── control_panel.png
├── control_panel_pinout.png
├── rj45.png
├── status_flow.png
├── status_flow_annotated.png
├── up_command.png
└── up_command_annotated.png
├── notes.txt
├── src
├── AlzaET1Ng.cpp
└── AlzaET1Ng.h
└── tests
├── AlzaEt1Ng_unittest.cpp
├── CMakeLists.txt
└── arduino_mock
└── CMakeLists.txt
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This is the name of the workflow, visible on GitHub UI.
2 | name: Main build
3 |
4 | on: [push, pull_request]
5 |
6 | # This is the list of jobs that will be run concurrently.
7 | # Since we use a build matrix, the actual number of jobs
8 | # started depends on how many configurations the matrix
9 | # will produce.
10 | jobs:
11 | build-job:
12 | strategy:
13 | matrix:
14 | arduino-board-fqbn:
15 | - esp32:esp32:lolin32
16 | include:
17 | - arduino-boards-fqbn: esp32:esp32:lolin32
18 | platform-url: https://dl.espressif.com/dl/package_esp32_index.json
19 | arduino-platform: "esp32:esp32"
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@main
26 |
27 | - name: Setup Arduino CLI
28 | uses: arduino/setup-arduino-cli@v1.1.1
29 |
30 | # We then install the platform, which one will be determined
31 | # dynamically by the build matrix.
32 | - name: Install platform
33 | run: |
34 | arduino-cli core --additional-urls ${{ matrix.platform-url }} update-index
35 | arduino-cli core --additional-urls ${{ matrix.platform-url }} install ${{ matrix.arduino-platform }}
36 | arduino-cli lib install u8g2
37 | # apt-get install make g++
38 |
39 | # Finally, we compile the sketch, using the FQBN that was set
40 | # in the build matrix.
41 | - name: Compile Sketch
42 | run: |
43 | cp WiFiConfig.h.sample WiFiConfig.h
44 | arduino-cli compile --log-level info --fqbn ${{ matrix.arduino-board-fqbn }} ./alzaergo
45 |
46 | - name: Install test dependencies
47 | run: |
48 | sudo apt-get install make g++ cmake lcov
49 |
50 | - name: Build tests
51 | run: |
52 | mkdir build && cd build
53 | cmake ../tests
54 | make
55 | cd ..
56 |
57 | - name: Run tests
58 | run: build/test-all
59 |
60 | - name: Collect Coverage Data
61 | run: |
62 | cd build
63 | lcov --capture --directory . --include '*AlzaET1Ng.cpp' --output-file coverage.info
64 | genhtml coverage.info --output-directory coverage_data
65 |
66 | - name: Archive coverage data
67 | uses: actions/upload-artifact@v2
68 | with:
69 | name: code-coverage-report
70 | path: build/coverage_data
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | WifiConfig.h
--------------------------------------------------------------------------------
/.vscode/arduino.json:
--------------------------------------------------------------------------------
1 | {
2 | "board": "espressif:esp32:lolin32",
3 | "sketch": "alzaergo.ino",
4 | "programmer": "AVR ISP",
5 | "port": "COM13",
6 | "configuration": "FlashFreq=40,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600"
7 | }
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Linux",
5 | "includePath": [
6 | "/home/mmrazik/arduino-1.8.13/tools/**",
7 | "/home/mmrazik/Arduino/libraries/**",
8 | "/home/mmrazik/arduino-1.8.13/libraries/**",
9 | "/home/mmrazik/arduino-1.8.13/hardware/tools/**",
10 | "/home/mmrazik/arduino-1.8.13/hardware/arduino/avr/**",
11 | "/home/mmrazik/arduino-1.8.12/hardware/arduino/avr/**",
12 | "/home/mmrazik/arduino-1.8.12/hardware/tools/**",
13 | "/home/mmrazik/arduino-1.8.12/libraries/**",
14 | "/home/mmrazik/arduino-1.8.12/tools/**",
15 | ],
16 | "forcedInclude": [
17 | "/home/mmrazik/arduino-1.8.12/hardware/arduino/avr/cores/arduino/Arduino.h",
18 | ],
19 | "defines": [
20 | "USBCON"
21 | ],
22 | "intelliSenseMode": "linux-gcc-x64",
23 | "compilerPath": "/usr/bin/gcc",
24 | "cStandard": "gnu17",
25 | "cppStandard": "gnu++14"
26 | },
27 | {
28 | "name": "Win32",
29 | "includePath": [
30 | "${workspaceFolder}\\src\\**",
31 | "C:\\Users\\martinmrazik\\OneDrive - Microsoft\\Documents\\Arduino\\hardware\\espressif\\esp32\\**",
32 | "C:\\Program Files (x86)\\Arduino\\tools\\**",
33 | "C:\\Users\\martinmrazik\\OneDrive - Microsoft\\Documents\\Arduino\\libraries\\**",
34 | "C:\\Program Files (x86)\\Arduino\\libraries\\**",
35 | "C:\\Program Files (x86)\\Arduino\\hardware\\tools\\**",
36 | "C:\\Program Files (x86)\\Arduino\\hardware\\arduino\\avr\\**"
37 | ],
38 | "forcedInclude": [],
39 | "defines": [
40 | "USBCON"
41 | ]
42 | }
43 | ],
44 | "version": 4
45 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "array": "cpp",
4 | "hash_map": "cpp",
5 | "deque": "cpp",
6 | "list": "cpp",
7 | "string": "cpp",
8 | "unordered_map": "cpp",
9 | "vector": "cpp",
10 | "string_view": "cpp",
11 | "initializer_list": "cpp",
12 | "ranges": "cpp",
13 | "mutex": "cpp",
14 | "shared_mutex": "cpp"
15 | }
16 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AlzaErgo Table ET1 NewGen
2 | This is my attempt to reverse engineer [AlzaErgo Table ET1 NewGen](https://www.alza.cz/alzaergo-table-et1-newgen-white-d5647311.htm) standing desk and add wi-fi functionality using something like Wemos D1/ESP8266.
3 |
4 | The work is in progress.
5 |
6 | # Components
7 | ### Control Panel
8 | Controls height of the desk, show the current height on display, has memory for 3 positions, you can set min/max height, etc.
9 |
10 |
11 |
12 | ### Control Box
13 | Contains the PowerSupply Unit, controls the motors and, gets commands from the Control Panel.
14 |
15 |
16 |
17 | ### Connection
18 | The control box has a RJ-45 port. It seems like all 8 pins are being used but the provided Control Panel is only using 5 of them and does not wire the rest at all.
19 |
20 |
21 |
22 | The wiring of the RJ45 port is as follows:
23 |
24 | ```
25 | 1. D-TX (green wire)
26 | 2. D-RX (brown wire; control panel transmits here)
27 | 3. unused
28 | 4. KEY-1 (white wire)
29 | 5. G (red wire)
30 | 6. unused
31 | 7. unused
32 | 8. +5V (yellow wire)
33 | ```
34 |
35 |
36 |
37 |
38 | Control Panel from the back:
39 |
40 |
41 | ## Protocol
42 | The control flow is relatively straightforward and it is an endless loop of request (from control panel) and response (from control box) over
43 | serial line. The serial line operates at 9600 bauds (8bit per frame, 1 stop bit, no parity bit, least significant bit first).
44 |
45 | The request always starts with `0xA5` and uses the following structure:
46 | ``0xA5 [byte1] [byte2] 0x01 [checksum]``
47 |
48 | The checksum is calculated as (where `&` is bitwise `AND`):
49 | ```(byte1 + byte2 + 0x01) & 0xff```
50 |
51 | The response always starts with `0x5A` and uses the following structure:
52 | ```0x5A [byte1] [byte2] [byte3] [byte4] [checksum]```
53 |
54 | Checksum in this case is ```(byte1 + byte2 + byte3 + byte4]) & 0xff```. The 3 bytes in response are the 3 characters displayed on the control panel and an
55 | optional decimal point.
56 |
57 | In addition to the serial communication there is also another channel (`Key`) which indicates a pressed button.
58 |
59 | Here are two examples of communication between the panel and board
60 |
61 | ### Status (no action)
62 | If there is no button pressed the `Key` pin is `LOW`, Control panel sends `0xA5 0x00 0x00 0x01 0x01` ("status") and the Control box replies with what to show on the built-in display (more on that later):
63 | 
64 |
65 |
66 | Here is another example while pressing the `UP` button on the control panel. In this case the `Key` pin is `HIGH`, control panel sends `0xA5 0x00 0x20 0x01 0x21` and Control box replies with what to show on the built-in display:
67 | 
68 |
69 |
70 | ### Control panel commands (requests)
71 |
72 | Panel sends one of the following commands:
73 | ```
74 | 0xA5 0x00 0x00 0x01 0x01 Idle/Get current display status
75 | 0xA5 0x00 0x20 0x01 0x21 Move up
76 | 0xA5 0x00 0x40 0x01 0x41 Move down
77 | 0xA5 0x00 0x60 0x01 0x61 UP and Down (used to reset)
78 | 0xA5 0x00 0x01 0x01 0x02 M button
79 | 0xA5 0x00 0x02 0x01 0x03 memory 1
80 | 0xA5 0x00 0x04 0x01 0x05 memory 2
81 | 0xA5 0x00 0x08 0x01 0x09 memory 3
82 | 0xA5 0x00 0x10 0x01 0x11 T button
83 | 0xA5 0x00 0x11 0x01 0x12 M+T (to get into settings)
84 | ```
85 |
86 |
87 | ### Control Box replies
88 | The control box reply always follow this structure: `0x5A [byte1] [byte2] [byte3] [byte4] [checksum]` and bytes 1 to 3 represent three digits/letters on 7 segment [display](https://en.wikipedia.org/wiki/Seven-segment_display). Each bit in the byte corresponds to one segment. For example 8 is represented by `0b01111111` (or `0b11111111`; the topmost bit is not relevant):
89 | ```
90 | __0_
91 | | |
92 | 5 | | 1
93 | |__6_|
94 | | |
95 | 4 | | 2
96 | |____|
97 | 3
98 | ```
99 |
100 | Digit 6 can be represented as 0b01111101 (or 0b11111101)
101 | ```
102 | __0_
103 | |
104 | 5 |
105 | |__6_
106 | | |
107 | 4 | | 2
108 | |____|
109 | 3
110 | ```
111 |
112 | The top most bit of the middle byte is signaling the decimal point.
113 | `0x06 0xbf 0x06` translates to `10.1` on display while `0x06 0x3f 0x06` translates to `101` on the display.
114 |
115 | Here is a list of digits (the later always represent the digit and a decimal point) but the control box sends more letters than just this one.
116 | | Digit | Hex | binary |
117 | |--- | --- | --- |
118 | | 0 | `0x3f` or `0xbf` | `0b00111111` or `0b10111111` |
119 | | 1 | `0x06` or `0x86` | `0b00000110` or `0b10000110` |
120 | | 2 | `0x5b` or `0xdb` | `0b01011011` or `0b11011011` |
121 | | 3 | `0x4f` or `0xcf` | `0b01001111` or `0b11001111` |
122 | | 4 | `0x66` or `0xe6` | `0b01100110` or `0b11100110` |
123 | | 5 | `0x6d` or `0xed` | `0b01101101` or `0b11101101` |
124 | | 6 | `0x7d` or `0xfd` | `0b01111101` or `0b11111101` |
125 | | 7 | `0x07` or `0x87` | `0b00000111` or `0b10000111` |
126 | | 8 | `0x7f` or `0xff` | `0b01111111` or `0b11111111` |
127 | | 9 | `0x6f` or `0xef` | `0b01101111` or `0b11101111` |
128 |
129 | The byte sequence of `5A 07 FD 6D 10 81 ` therefore corresponds to table height `76.5` cm (byte `07` corresponds to `7`, byte `FD` corresponds to `6.` and byte `6d` corresponds to `5`).
130 |
131 | `Byte4` can have the following values:
132 | | Value | Description |
133 | |--- |--- |
134 | | `0x00` | Everything on the display is completely off
135 | | `0x01` | Timer indicator is turned on
136 | | `0x10` | 7 segment display is on (3 characters + decimal point)
137 | | `0x11` | 7 segment display is on as well as the timer indicator
138 |
139 |
140 | ### Timing
141 | The request/reply over serial goes in a very quick succession (around 7ms per command or reply) and it seems like the control board is sensitive on the count of commands. E.g. I was unable to unlock the control panel from sleep (by holding `M` for 3 seconds) if the delay between commands was around `250ms`. Similarly the desk goes up/down slower if the delay between commands is longer (despite the fact the `Key` pin is still `HIGH`).
142 |
143 |
144 | ### Standby
145 | After 60 seconds of inactivity the control board starts sending `0x50 0x40 0x40 0x40 0x10 0xD0` which stands for Safety Standby Mode ("---" on the display). This mode can be also activated by holding down the `M` button for 3 seconds.
146 |
147 | After another 9 minutes (10 minutes of inactivity in total) the control board starts sending `0x5A 0x00 0x00 0x00 0x00 0x00` which is a signal to completely shutdown the display of control panel. This can be alternatively the sequence of `0x5A 0x00 0x00 0x00 0x01 0x01` which keeps the timer indicator led on.
148 |
149 | After 18 minutes of inactivity the board sends `0x5A 0xFF 0xFF 0xFF 0x00 0xFD` sequence which is a signal to control panel to stop sending the "status" sequence. From this point the traffic on serial line stops until the board is woken up again (by pushing any of the buttons).
--------------------------------------------------------------------------------
/WiFiConfig.h.sample:
--------------------------------------------------------------------------------
1 | const char* ssid = "REPLACE_WITH_YOUR_SSID";
2 | const char* password = "REPLACE_WITH_YOUR_PASSWORD";
--------------------------------------------------------------------------------
/alzaergo.ino:
--------------------------------------------------------------------------------
1 | //#include
2 | //#include
3 | #include
4 | #define ALZAET1NG_THREADSAFE
5 | #include "src/AlzaET1Ng.h"
6 | #include
7 | #include "WiFiConfig.h"
8 | #include "esp_http_server.h"
9 |
10 |
11 |
12 |
13 |
14 | #define KEY_PIN 22
15 | const int key_pins[] = {27, 14, 12, 13};
16 | #define KEY_UP 0
17 | #define KEY_DOWN 1
18 | #define KEY_M 2
19 |
20 | TaskHandle_t AlzaTask;
21 | AlzaET1Ng::ControlPanel AlzaControl(Serial2, KEY_PIN);
22 | httpd_handle_t httpServer;
23 |
24 | //U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
25 | U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 5, /* dc=*/ 32, /* reset=*/ 33);
26 |
27 | esp_err_t get_height_handler(httpd_req_t *req)
28 | {
29 | httpd_resp_send(req, String(AlzaControl.getHeight()).c_str(), HTTPD_RESP_USE_STRLEN);
30 | return ESP_OK;
31 | }
32 |
33 | httpd_uri_t uri_get_height = {
34 | .uri = "/height",
35 | .method = HTTP_GET,
36 | .handler = get_height_handler,
37 | .user_ctx = NULL
38 | };
39 |
40 |
41 | httpd_handle_t startWebServer(void)
42 | {
43 | httpd_config_t config = HTTPD_DEFAULT_CONFIG();
44 | httpd_handle_t server = NULL;
45 |
46 | if (httpd_start(&server, &config) == ESP_OK) {
47 | httpd_register_uri_handler(server, &uri_get_height);
48 | }
49 | return server;
50 | }
51 |
52 | void stop_webserver(httpd_handle_t server)
53 | {
54 | if (server) {
55 | httpd_stop(server);
56 | }
57 | }
58 |
59 | void AlzaMainLoop( void * parameter) {
60 | for(;;) {
61 | AlzaControl.handleLoop();
62 | }
63 | }
64 |
65 | void ConnectToWiFi()
66 | {
67 | WiFi.mode(WIFI_STA);
68 | WiFi.begin(ssid, password);
69 | Serial.print("Connecting to "); Serial.println(ssid);
70 |
71 | uint8_t i = 0;
72 | while (WiFi.status() != WL_CONNECTED)
73 | {
74 | Serial.print('.');
75 | delay(500);
76 |
77 | if ((++i % 16) == 0)
78 | {
79 | Serial.println(F(" still trying to connect"));
80 | }
81 | }
82 |
83 | Serial.print(F("Connected. My IP address is: "));
84 | Serial.println(WiFi.localIP());
85 | }
86 |
87 | void setup()
88 | {
89 | u8g2.begin();
90 |
91 |
92 | Serial.begin(115200);
93 | Serial.write("Hello world");
94 | for(int x=0; x<4; x++)
95 | {
96 | pinMode(key_pins[x], INPUT);
97 | }
98 |
99 | u8g2.clearBuffer();
100 | u8g2.setFont(u8g2_font_ncenB08_tr);
101 | u8g2.drawStr(15,10,"Hello World!");
102 | u8g2.sendBuffer();
103 |
104 | xTaskCreatePinnedToCore(
105 | AlzaMainLoop,
106 | "AlzaTask",
107 | 10000, /* Stack size in words */
108 | NULL, /* Task input parameter */
109 | 0, /* Priority of the task */
110 | &AlzaTask,
111 | 0); /* Core where the task should run */
112 |
113 | ConnectToWiFi();
114 | httpServer = startWebServer();
115 |
116 | }
117 |
118 | void writeln(String ln) {
119 | u8g2.clearBuffer();
120 | u8g2.setCursor(0,15);
121 | u8g2.print(ln);
122 | u8g2.sendBuffer();
123 | }
124 |
125 | int up, down, memory;
126 | unsigned long last_refresh = 0;
127 | char displayData[] = {0, 0, 0, 0, 0};
128 |
129 |
130 |
131 | void loop()
132 | {
133 |
134 | up = digitalRead(key_pins[KEY_UP]);
135 | down = digitalRead(key_pins[KEY_DOWN]);
136 | memory = digitalRead(key_pins[KEY_M]);
137 |
138 | if (up == HIGH) {
139 | AlzaControl.holdCommand(AlzaET1Ng::Commands::Up);
140 | } else if (down == HIGH) {
141 | AlzaControl.holdCommand(AlzaET1Ng::Commands::Down);
142 | } else if (memory == HIGH) {
143 | AlzaControl.holdCommand(AlzaET1Ng::Commands::M);
144 | } else {
145 | AlzaControl.holdCommand(AlzaET1Ng::Commands::Status);
146 | }
147 |
148 | u8g2.clearBuffer();
149 | u8g2.setFont(u8g2_font_ncenB14_tr);
150 | AlzaControl.getBcdDisplayAsString(displayData);
151 | u8g2.drawStr(35, 40, displayData);
152 | u8g2.setFont(u8g2_font_helvR08_te);
153 | u8g2.drawStr(10, 10, WiFi.localIP().toString().c_str());
154 | u8g2.sendBuffer();
155 | }
--------------------------------------------------------------------------------
/azure-pipelines-1.yml:
--------------------------------------------------------------------------------
1 | # Starter pipeline
2 | # Start with a minimal pipeline that you can customize to build and deploy your code.
3 | # Add steps that build, run tests, deploy, and more:
4 | # https://aka.ms/yaml
5 |
6 | trigger:
7 | - main
8 |
9 |
10 | pool:
11 | vmImage: ubuntu-latest
12 |
13 | steps:
14 | - script: echo Hello, world!
15 | displayName: 'Run a one-line script'
16 |
17 | - script: |
18 | echo Add other tasks to build, test, and deploy your project.
19 | echo See https://aka.ms/yaml
20 | - task: DotNetCoreCLI@2
21 | inputs:
22 | command: 'build'
23 | displayName: 'Run a multi-line script'
24 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # Starter pipeline
2 | # Start with a minimal pipeline that you can customize to build and deploy your code.
3 | # Add steps that build, run tests, deploy, and more:
4 | # https://aka.ms/yaml
5 |
6 | trigger:
7 | - main
8 |
9 | pool:
10 | vmImage: ubuntu-latest
11 |
12 | steps:
13 | - script: echo Hello, world!
14 | displayName: 'Run a one-line script'
15 |
16 | - script: |
17 | echo Add other tasks to build, test, and deploy your project.
18 | echo See https://aka.ms/yaml
19 | displayName: 'Run a multi-line script'
20 |
--------------------------------------------------------------------------------
/docs/control_panel_manual.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/docs/control_panel_manual.pdf
--------------------------------------------------------------------------------
/images/control_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/control_box.png
--------------------------------------------------------------------------------
/images/control_box_pins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/control_box_pins.png
--------------------------------------------------------------------------------
/images/control_panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/control_panel.png
--------------------------------------------------------------------------------
/images/control_panel_pinout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/control_panel_pinout.png
--------------------------------------------------------------------------------
/images/rj45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/rj45.png
--------------------------------------------------------------------------------
/images/status_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/status_flow.png
--------------------------------------------------------------------------------
/images/status_flow_annotated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/status_flow_annotated.png
--------------------------------------------------------------------------------
/images/up_command.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/up_command.png
--------------------------------------------------------------------------------
/images/up_command_annotated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmrazik/alzaergo/e88ef6d8f8407e09b1c57f5394f42abd6f04d830/images/up_command_annotated.png
--------------------------------------------------------------------------------
/notes.txt:
--------------------------------------------------------------------------------
1 | Board only:
2 | Seem to send nothing at all
3 | After pluggin in control
4 | A0 5 1 1 41 41 69 1 1 1 41 41 4F 65 5B 3F 10 B0 5A 6 5B 3F 10 B0
5 | 38 5A 0 0 0 10 10 5A 0 0 0 10 10 5A 6 5B 3F 10 B0
6 |
7 | Err 4 blinking for a while 79 3F 66 == E04
8 |
9 | 5A 6 5B 3F 10 B0
10 | 5A 79 3F 66 10 2E E04
11 | 5A 79 3F 66 10 2E
12 | 5A 79 3F 66 10 2E
13 | 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E 5A 79 3F 66 10 2E
14 |
15 |
16 |
17 | Panel only:
18 | F8 FF C0 8 A5 0 0 1 1
19 | 2 F1 A5 0 0 1 1
20 | 18 A0 1A 1 5 5 4A 0 0 1 1 A5 0 0 1 1
21 |
22 |
23 |
24 | 79 3F 66
25 |
26 |
27 |
28 | Board:
29 | 5A 7D .. 3F 10 ..
30 |
31 |
32 | 62cm: DB .. .. A7
33 | 65cm: ED .. .. B9
34 |
35 |
36 | s: CF .. .. 9B
37 | l: CF .. .. 9B
38 |
39 | ----------------
40 | 62cm to 63 cm
41 | 6th - 4th = 0x68
42 |
43 | 5A 7D DB 6 10 6E (62.x cm)
44 | ..
45 | 5A 7D DB 5B 10 C3
46 | ..
47 | 5A 7D DB 4F 10 B7
48 | ..
49 | 5A 7D DB 66 10 CE
50 | ..
51 | 5A 7D DB 6D 10 D5
52 | ..
53 | 5A 7D DB 7D 10 E5
54 | ..
55 | 5A 7D DB 7F 10 E7
56 | ..
57 | 5A 7D DB 6F 10 D7
58 | ..
59 | 5A 7D CF 3F 10 9B (many times; 63.0)
60 | -------------------
61 |
62 | -----------------
63 | 63 to 87cm
64 | 5A 7D CF 6 10 62 (63.x cm)
65 | 5A 7D CF 5B 10 B7
66 | 5A 7D CF 4F 10 AB
67 | 5A 7D CF 66 10 C2
68 | 5A 7D CF 6D 10 C9
69 | 5A 7D CF 7D 10 D9
70 | 5A 7D CF 7 10 63
71 | 5A 7D CF 7F 10 DB
72 | 5A 7D CF 7F 10 10 81
73 | 5A 7D ED 7 10 81 (many times/; probably at 64.x cm)
74 | 57 D5 F5 6D 10 (many times)
75 | ... probably noise
76 | 5A 7D FF 3F 10 CB
77 | 5A 7D FF 6 10 92
78 |
79 | 5A 7D FF 5B 10 E7
80 | 5A 7D FF 4F 10 DB
81 | 5A 7D FF 66 10 52
82 | D5 FD 66 10 52
83 | D5 FD 6D 10 49
84 | D5 FD 6D 10 49
85 | D5 FD 7D 10 9
86 |
87 | 5A 7D FF 7D 10 9
88 | 5A 7D FF 7 10 93
89 |
90 | 5A 7D FF 7F 10 B
91 | 5A 7D FF 6F 10 4B
92 | D5 FD 6F 10 4B
93 |
94 |
95 |
96 | end:
97 |
98 | 5A 7F DB 66 10 D0
99 |
100 | 5A 7F DB 6D 10 D7
101 |
102 | 5A 7F DB 7D 10 E7
103 |
104 | 5A 7F 87 6 10 1C (87.1cm)
105 |
106 | --------------------
107 |
108 | 87.1cm to 90.xcm
109 | 5A 7F FF 7D 10 B
110 | 5A 7F EF 7 10 85
111 | 5A 7F EF 6F 10 ED
112 |
113 | 5A 6F BF 3F 10 7D (?90.0)
114 | 5A 6F BF 6 10 44 (?90.1)
115 | 5A 6F BF 5B 10 99 (90.2cm)
116 |
117 |
118 |
119 | ---------
120 | 90.2 to 128
121 | stops at 100, 111, 128cm
122 |
123 | 5A 6 6 7D 10 99
124 | 5A 6 5B 6 10 77
125 | 5A 6 5B 5B 10 CC
126 | 5A 6 5B 4F 10 C0 (128 cm)
127 |
128 |
129 | 7D -> 7 -> 7F -> 6F -> 6
130 |
131 | DB -> CF -> E6 -> ED -> FD ->
132 |
133 |
134 | ----------------------------
135 | 5A 7D DB 3F 10 A7 62.0 cm
136 | 5A 7D CF 3F 10 9B 63.0 cm
137 | 5A 7D E6 3F 10 B2 64.0 cm
138 | 5A 7D ED 3F 10 B9 65.0 cm
139 | 5A 7D FD 3F 10 C9 66.0 cm
140 | 5A 7D 87 3F 10 53 67.0 cm
141 | 5A 7D FF 3F 10 CB 68.0 cm
142 | 5A 7D EF 3F 10 BB 69.0 cm
143 | 5A 07 BF 3F 10 15 70.0 cm
144 | 5A 07 86 3F 10 DC 71.0 cm
145 | 5A 07 DB 3F 10 31 72.0 cm
146 | 5A 07 CF 3F 10 25 73.0 cm
147 | 5A 07 E6 3F 10 3C 74.0 cm
148 | 5A 07 E6 5B 10 58 74.2 cm
149 | 5A 07 E6 6D 10 6A 74.5 cm
150 | 5A 07 E6 6F 10 6C 74.9 cm
151 | 5A 07 ED 3F 10 43 75.0 cm
152 | 5A 07 ED 4F 10 53 75.3 cm
153 | 5A 07 ED 6D 10 71 75.5 cm
154 | 5A 07 ED 6F 10 73 75.9 cm
155 | 5A 07 FD 3F 10 53 76.0 cm
156 | 5A 07 FD 06 10 1A 76.1 cm
157 | 5A 07 FD 5B 10 6F 76.2 cm
158 | 5A 07 FD 4F 10 63 76.3 cm
159 | 5A 07 FD 66 10 7A 76.4 cm
160 | 5A 07 FD 6D 10 81 76.5 cm
161 | 5A 07 FD 7D 10 91 76.6 cm
162 | 5A 07 FD 07 10 1B 76.7 cm
163 | 5A 07 FD 7F 10 93 76.8 cm
164 | 5A 07 FD 6F 10 83 76.9 cm
165 | 5A 07 87 3F 10 DD 77.0 cm
166 | 5A 07 EF 6F 10 75 79.9 cm 5A 07 EF 6F 10 .. 79.9 cm
167 | 5A 7F BF 3F 10 8D 80.0 cm
168 | 5A 7F DB 3F 10 A9 82.0 cm
169 | 5A 6F BF 3F 10 7D 90.0 cm
170 | 5A 6F EF 6F 10 DD 99.9 cm
171 | 5A 06 3F 3F 10 94 100 cm
172 | 5A 06 3F 5B 10 B0 102 cm
173 | 5A 06 06 3F 10 5B 110 cm
174 | 5A 06 5B 3F 10 B0 120 cm
175 |
176 | _
177 | |_|
178 | |_|
179 |
180 |
181 |
182 |
183 | 0b01000100
184 | 0b01010011
185 | 0b00010111
186 |
--------------------------------------------------------------------------------
/src/AlzaET1Ng.cpp:
--------------------------------------------------------------------------------
1 | #include "AlzaET1Ng.h"
2 | #include "Arduino.h"
3 | using namespace AlzaET1Ng;
4 |
5 | const char AlzaET1Ng::bcdDigitToString(int bcd_code)
6 | {
7 | bcd_code = bcd_code & 0x7f; // we don't care about the top-most bit
8 | switch (bcd_code) {
9 | case 0x3f:
10 | return '0';
11 | case 0x06:
12 | return '1';
13 | case 0x5b:
14 | return '2';
15 | case 0x4f:
16 | return '3';
17 | case 0x66:
18 | return '4';
19 | case 0x6d:
20 | return '5';
21 | case 0x7d:
22 | return '6';
23 | case 0x07:
24 | return '7';
25 | case 0x7f:
26 | return '8';
27 | case 0x6f:
28 | return '9';
29 | case 0x40:
30 | return '-';
31 | case 0x79:
32 | return 'E';
33 | case 0x50:
34 | return 'r';
35 | case 0x31:
36 | return 'T';
37 | case 0x76:
38 | return 'H';
39 | case 0x5c:
40 | return 'o';
41 | case 0x78:
42 | return 't';
43 | case 0x00:
44 | return ' ';
45 | }
46 | return '?';
47 | }
48 |
49 |
50 | ControlPanel::ControlPanel(HardwareSerial &hws, int key):
51 | serial(hws) {
52 | pinMode(key, OUTPUT);
53 | digitalWrite(key, LOW);
54 | keyPin = key;
55 | #ifdef ALZAET1NG_THREADSAFE
56 | mutex = xSemaphoreCreateMutex();
57 | if (mutex == NULL) {
58 | // do something here
59 | }
60 | #endif
61 |
62 | //serial.begin(9600);
63 | serial.begin(9600, SERIAL_8N1, 16, 17);
64 | nextCommand = Commands::Status;
65 | }
66 |
67 |
68 | bool ControlPanel::isValidResponse(int response[]) {
69 | unsigned int checksum = 0;
70 | for (int i=2; i < RESPONSE_SIZE; i++) {
71 | checksum = (checksum + response[i]) & 0xff;
72 | }
73 | return checksum == response[RESPONSE_SIZE];
74 | }
75 |
76 |
77 | void ControlPanel::sendCommand(Commands cmd) {
78 | if (waitForResponse) {
79 | return;
80 | }
81 | // Command is 5 bytes:
82 | // [command_header] 0x0 [command] 0x01 [checksum]
83 | // e.g. 0xa5, 0x00, 0x20, 0x01, 0x21
84 | serial.write((uint8_t) COMMAND_HEADER);
85 | serial.write((uint8_t) 0x0);
86 | serial.write((uint8_t) cmd);
87 | serial.write((uint8_t) 0x01);
88 | serial.write((uint8_t) cmd + 1); // this is potentially wrong but all the known commands are smaller than 254 so this will give the rigth result
89 |
90 | lastCommandExecution = millis();
91 | waitForResponse = true;
92 | }
93 |
94 |
95 | int ControlPanel::bcdToHeight() {
96 | // height is unfortunately unitless depending on the configuration of the control box
97 | // (holding T button for 8 seconds switches between inches and cm). This method
98 | // just converts whatever is displayed to an integer in the given unit
99 |
100 | // TODO: fix this for inches. If the display shows 72.5, we return 725 from here which is milimeters
101 | // when the table operates in inches and the display shows 32.4 we return 324 which is a unit that does
102 | // not make much sense
103 | int new_height = 0;
104 |
105 | // validate the display; it might show an error or something similar
106 | int i = 0;
107 | while (displayStatusString[i]) {
108 | // if we have some non-digit character it is probably error or some other status message
109 | if (!((displayStatusString[i] >= '0' && displayStatusString[i] <= '9') || (displayStatusString[i] == '.'))) {
110 | return 0;
111 | }
112 | i = i + 1;
113 | }
114 |
115 | new_height = (displayStatusString[0] - '0');
116 | new_height = new_height * 10 + (displayStatusString[1] - '0');
117 | if (displayStatusString[2] == '.') {
118 | new_height = new_height * 10 + (displayStatusString[3] - '0');
119 | } else {
120 | new_height = new_height * 10 + (displayStatusString[2] - '0');
121 | new_height = new_height * 10;
122 | }
123 |
124 | return new_height;
125 | }
126 |
127 |
128 | void ControlPanel::updateRepresentations() {
129 | #ifdef ALZAET1NG_THREADSAFE
130 | xSemaphoreTake(mutex, portMAX_DELAY);
131 | #endif
132 |
133 | // raw (7 segment display)
134 | displayStatus[0] = responseBuffer[2];
135 | displayStatus[1] = responseBuffer[3];
136 | displayStatus[2] = responseBuffer[4];
137 |
138 | // 7 segment display to ASCII
139 | int position = 0;
140 | displayStatusString[position++] = bcdDigitToString(displayStatus[0]);
141 | displayStatusString[position++] = bcdDigitToString(displayStatus[1]);
142 |
143 | // If the middle character is '-' and the first one is '5', we are in the settings
144 | // The 7 segment display shows '5' and 'S' as the same character but they are not
145 | // the same. Be a bit smarter here and change the '5' to 'S'
146 | if ((displayStatusString[1] == '-') && (displayStatusString[0] == '5')) {
147 | displayStatusString[0] = 'S';
148 | }
149 |
150 | // if the middle digit has top most bit set to 1, it means we have decimal point
151 | if ((displayStatus[1] & 0b10000000) == 0b10000000) {
152 | displayStatusString[position++] = '.';
153 | }
154 | displayStatusString[position++] = bcdDigitToString(displayStatus[2]);
155 | displayStatusString[position] = 0;
156 |
157 | int new_height = bcdToHeight();
158 | if (new_height != 0) {
159 | height = new_height;
160 | }
161 | #ifdef ALZAET1NG_THREADSAFE
162 | xSemaphoreGive(mutex);
163 | #endif
164 | }
165 |
166 | void ControlPanel::handleIncomingResponse() {
167 | if (serial.available()) {
168 | int c = serial.read();
169 | if (c == RESPONSE_HEADER) {
170 | responseBuffer[0] = 1;
171 | responseBuffer[1] = c;
172 | } else if ((responseBuffer[0] >= 1) && (responseBuffer[0] < RESPONSE_SIZE)) {
173 | responseBuffer[++responseBuffer[0]] = c;
174 | } else {
175 | waitForResponse = false;
176 | }
177 | if (responseBuffer[0] == RESPONSE_SIZE) {
178 | waitForResponse = false;
179 | if (isValidResponse(responseBuffer)) {
180 | updateRepresentations();
181 | #ifdef ALZAET1NG_THREADSAFE
182 | // This is just an assumption that when we are running in the THREADSAFE mode
183 | // we run on a dedicated core/task. In that case we need to call delay every now and then
184 | // otherwise the watchdog timer will be triggered and will kill the task
185 |
186 | // TODO: try to figure out if there is a way to just feed the watchdog and not do a 1ms delay
187 | // ~0.5ms delay might be sill desirable so we do not send next command immediately after the receive.
188 | // The originial control panel seem to be sending a command every ~7ms (which is approcimately
189 | // 1 command transmittion time + something on top)
190 | delay(1);
191 | #endif
192 | } // else invalid response -- nothing we can do here; ignore and let the controller send the nextCommand
193 | }
194 | }
195 | }
196 |
197 |
198 | void ControlPanel::detectStaleHeight() {
199 |
200 | }
201 |
202 | void ControlPanel::handleLoop() {
203 | #ifdef DEBUG
204 | if ((millis() - lastUpdate) > 50) {
205 | lastUpdate = millis();
206 | Serial.write(String(lastUpdate).c_str());
207 | Serial.write("\r\n");
208 | }
209 | #endif
210 | sendCommand(nextCommand);
211 | handleIncomingResponse();
212 | detectStaleHeight();
213 | evaluateTargetHeight();
214 |
215 |
216 | // if we are in some weird state, still waiting for a response but nothing comes in, stop waiting for response
217 | if ((millis() - lastCommandExecution) > WAIT_FOR_RESPONSE_TIMEOUT) {
218 | waitForResponse = false;
219 | }
220 | }
221 |
222 |
223 | void ControlPanel::holdCommand(Commands cmd) {
224 | if (cmd == Commands::Status) {
225 | digitalWrite(keyPin, LOW);
226 | } else {
227 | digitalWrite(keyPin, HIGH);
228 | }
229 | nextCommand = cmd;
230 | }
231 |
232 |
233 | void ControlPanel::getBcdDisplay(int *data) {
234 | #ifdef ALZAET1NG_THREADSAFE
235 | xSemaphoreTake(mutex, portMAX_DELAY);
236 | #endif
237 | data[0] = displayStatus[0];
238 | data[1] = displayStatus[1];
239 | data[2] = displayStatus[2];
240 | #ifdef ALZAET1NG_THREADSAFE
241 | xSemaphoreGive(mutex);
242 | #endif
243 | }
244 |
245 |
246 | void ControlPanel::getBcdDisplayAsString(char *data) {
247 | #ifdef ALZAET1NG_THREADSAFE
248 | xSemaphoreTake(mutex, portMAX_DELAY);
249 | #endif
250 | data[0] = displayStatusString[0];
251 | data[1] = displayStatusString[1];
252 | data[2] = displayStatusString[2];
253 | data[3] = displayStatusString[3];
254 | #ifdef ALZAET1NG_THREADSAFE
255 | xSemaphoreGive(mutex);
256 | #endif
257 | }
258 |
259 |
260 | int ControlPanel::getHeight() {
261 | #ifdef ALZAET1NG_THREADSAFE
262 | xSemaphoreTake(mutex, portMAX_DELAY);
263 | #endif
264 | return height;
265 | #ifdef ALZAET1NG_THREADSAFE
266 | xSemaphoreGive(mutex);
267 | #endif
268 | }
269 |
270 |
271 | void ControlPanel::evaluateTargetHeight() {
272 | if (targetHeight != 0) {
273 | if ((nextCommand == Commands::Up) && (height >= targetHeight)) {
274 | holdCommand(Commands::Status);
275 | } else
276 | if ((nextCommand == Commands::Down) && (height <= targetHeight)) {
277 | holdCommand(Commands::Status);
278 | }
279 | }
280 | }
281 |
282 |
283 | void ControlPanel::setHeight(int newHeight) {
284 | targetHeight = newHeight;
285 | if (newHeight > height) {
286 | holdCommand(Commands::Up);
287 | } else
288 | if (newHeight < height) {
289 | holdCommand(Commands::Down);
290 | }
291 | }
292 |
293 | Commands ControlPanel::getNextCommand() {
294 | return nextCommand;
295 | }
296 |
297 | bool ControlPanel::waitingForResponse() {
298 | return waitForResponse;
299 | }
300 |
--------------------------------------------------------------------------------
/src/AlzaET1Ng.h:
--------------------------------------------------------------------------------
1 | #ifndef AlzaET1Ng_h
2 | #define AlzaET1Ng_h
3 | #include "Arduino.h"
4 |
5 | #ifdef ALZAET1NG_THREADSAFE
6 | #include "freertos/semphr.h"
7 | #endif
8 |
9 |
10 |
11 | namespace AlzaET1Ng
12 | {
13 | const char bcdDigitToString(int bcd_code);
14 |
15 | static const int COMMAND_SIZE = 5;
16 | static const int RESPONSE_SIZE = 6;
17 | static const int RESPONSE_HEADER = 0x5A;
18 | static const int COMMAND_HEADER = 0xA5;
19 | static const unsigned long WAIT_FOR_RESPONSE_TIMEOUT = 200;
20 | enum Commands {
21 | Status = 0x00,
22 | Up = 0x20,
23 | Down = 0x40,
24 | M = 0x01,
25 | M1 = 0x02,
26 | M2 = 0x04,
27 | M3 = 0x08,
28 | T = 0x10,
29 | Reset = 0x60
30 | };
31 |
32 | class ControlPanel
33 | {
34 | private:
35 | int displayStatus[3] = {0, 0, 0};
36 | char displayStatusString[5] = {0, 0, 0, 0, 0};
37 | int height = 0;
38 | #ifdef ALZAET1NG_THREADSAFE
39 | SemaphoreHandle_t mutex;
40 | #endif
41 |
42 | HardwareSerial &serial;
43 | int keyPin;
44 |
45 | Commands nextCommand;
46 | int targetHeight = 0;
47 | int oldHeight = 0;
48 | bool waitForResponse = false;
49 | unsigned long lastCommandExecution = 0;
50 | int responseBuffer[RESPONSE_SIZE + 1] = {0, 0, 0, 0, 0, 0, 0};
51 |
52 | #ifdef DEBUG
53 | unsigned long lastUpdated = 0;
54 | #endif
55 |
56 | bool isValidResponse(int response[]);
57 | void updateRepresentations();
58 | int bcdToHeight();
59 | void evaluateTargetHeight();
60 | void detectStaleHeight();
61 |
62 | public:
63 | // TODO: Add a constructor for NeoSWSerial/SoftwareSerial so this can be used with SW serial too
64 | ControlPanel(HardwareSerial &srl, int key);
65 | void sendCommand(Commands cmd);
66 | void setHeight(int newHeight);
67 | void handleLoop();
68 | void holdCommand(Commands cmd);
69 | void getBcdDisplay(int *data);
70 | void getBcdDisplayAsString(char *data);
71 | int getHeight();
72 |
73 | // public to make testing easier
74 | void handleIncomingResponse();
75 |
76 | // mostly for testing
77 | Commands getNextCommand();
78 | bool waitingForResponse();
79 | };
80 | };
81 | #endif
--------------------------------------------------------------------------------
/tests/AlzaEt1Ng_unittest.cpp:
--------------------------------------------------------------------------------
1 | #include "gtest/gtest.h"
2 | #include
3 | #include "gmock/gmock-matchers.h"
4 | #include "arduino-mock/Arduino.h"
5 | #include "arduino-mock/Serial.h"
6 |
7 |
8 |
9 | typedef Serial_ HardwareSerial;
10 |
11 | #include "../src/AlzaET1Ng.h"
12 |
13 | using ::testing::Return;
14 | #define KEY_PIN 22
15 |
16 | class BasicTestFixture : public ::testing::Test {
17 | protected:
18 | ArduinoMock *arduinoMock;
19 | SerialMock *serialMock;
20 |
21 | virtual void SetUp() {
22 | arduinoMock = arduinoMockInstance();
23 | serialMock = serialMockInstance();
24 | EXPECT_CALL(*arduinoMock, pinMode(KEY_PIN, OUTPUT));
25 | EXPECT_CALL(*arduinoMock, digitalWrite(KEY_PIN, 0));
26 | EXPECT_CALL(*serialMock, begin(9600));
27 | }
28 |
29 | virtual void TearDown() {
30 | releaseSerialMock();
31 | releaseArduinoMock();
32 | }
33 | };
34 |
35 | class TestFixtureWithSerialData : public BasicTestFixture {
36 | protected:
37 | void SetSerialMessage(const int *response, int size) {
38 | EXPECT_CALL(*serialMock, available())
39 | .Times(size)
40 | .WillRepeatedly(Return(true));
41 |
42 | EXPECT_CALL(*serialMock, read())
43 | .WillOnce(Return(response[0]))
44 | .WillOnce(Return(response[1]))
45 | .WillOnce(Return(response[2]))
46 | .WillOnce(Return(response[3]))
47 | .WillOnce(Return(response[4]))
48 | .WillOnce(Return(response[5]));
49 |
50 | }
51 | };
52 |
53 | TEST_F(BasicTestFixture, holdCommand) {
54 | EXPECT_CALL(*arduinoMock, digitalWrite(KEY_PIN, 1));
55 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
56 | AlzaControl.holdCommand(AlzaET1Ng::Commands::Down);
57 | ASSERT_EQ(AlzaControl.getNextCommand(), AlzaET1Ng::Commands::Down);
58 | }
59 |
60 | TEST_F(BasicTestFixture, holdCommandStatus) {
61 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
62 | EXPECT_CALL(*arduinoMock, digitalWrite(KEY_PIN, 0));
63 | AlzaControl.holdCommand(AlzaET1Ng::Commands::Status);
64 | ASSERT_EQ(AlzaControl.getNextCommand(), AlzaET1Ng::Commands::Status);
65 | }
66 |
67 | TEST_F(BasicTestFixture, waitsForResponseAfterStartup) {
68 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
69 | EXPECT_CALL(*serialMock, write(testing::An())).Times(AlzaET1Ng::COMMAND_SIZE);
70 | EXPECT_CALL(*arduinoMock, millis()).Times(2);
71 | EXPECT_CALL(*serialMock, available()).WillOnce(Return(false));
72 | AlzaControl.handleLoop();
73 | ASSERT_EQ(AlzaControl.waitingForResponse(), true);
74 | }
75 |
76 |
77 | TEST_F(BasicTestFixture, waitForResponseTimesOut) {
78 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
79 | EXPECT_CALL(*serialMock, write(testing::An())).Times(AlzaET1Ng::COMMAND_SIZE);
80 | EXPECT_CALL(*arduinoMock, millis())
81 | .WillOnce(Return(0))
82 | .WillOnce(Return(0));
83 | EXPECT_CALL(*serialMock, available()).WillOnce(Return(false));
84 | AlzaControl.handleLoop();
85 | ASSERT_EQ(AlzaControl.waitingForResponse(), true);
86 | EXPECT_CALL(*arduinoMock, millis()).WillOnce(Return(AlzaET1Ng::WAIT_FOR_RESPONSE_TIMEOUT + 1));
87 | EXPECT_CALL(*serialMock, available()).WillOnce(Return(false));
88 | AlzaControl.handleLoop();
89 | ASSERT_EQ(AlzaControl.waitingForResponse(), false);
90 | }
91 |
92 |
93 | TEST_F(TestFixtureWithSerialData, handleIncomingResponse) {
94 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
95 | // Corresponds to 76.5
96 | int message[] = {AlzaET1Ng::RESPONSE_HEADER, 0x7, 0xfd, 0x6d, 0x10, 0x81};
97 | SetSerialMessage(message, AlzaET1Ng::RESPONSE_SIZE);
98 | for (int i=0; i < AlzaET1Ng::RESPONSE_SIZE; i++) {
99 | AlzaControl.handleIncomingResponse();
100 | }
101 | int data[3];
102 | AlzaControl.getBcdDisplay(data);
103 | ASSERT_EQ(data[0], 0x7);
104 | ASSERT_EQ(data[1], 0xfd);
105 | ASSERT_EQ(data[2], 0x6d);
106 |
107 | ASSERT_EQ(AlzaControl.getHeight(), 765);
108 |
109 | char str_data[4];
110 | AlzaControl.getBcdDisplayAsString(str_data);
111 | ASSERT_EQ(str_data[0], '7');
112 | ASSERT_EQ(str_data[1], '6');
113 | ASSERT_EQ(str_data[2], '.');
114 | ASSERT_EQ(str_data[3], '5');
115 | }
116 |
117 |
118 | TEST_F(TestFixtureWithSerialData, handleIncomingResponseWithWrongChecksum) {
119 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
120 | // Corresponds to 76.5
121 | int message[] = {AlzaET1Ng::RESPONSE_HEADER, 0x7, 0xfd, 0x6d, 0x10, 0x82};
122 | SetSerialMessage(message, AlzaET1Ng::RESPONSE_SIZE);
123 | for (int i=0; i < AlzaET1Ng::RESPONSE_SIZE; i++) {
124 | AlzaControl.handleIncomingResponse();
125 | }
126 | int data[3];
127 | AlzaControl.getBcdDisplay(data);
128 | ASSERT_EQ(data[0], 0x0);
129 | ASSERT_EQ(data[1], 0x0);
130 | ASSERT_EQ(data[2], 0x0);
131 | }
132 |
133 |
134 | TEST_F(TestFixtureWithSerialData, testSettingsString) {
135 | AlzaET1Ng::ControlPanel AlzaControl(Serial, KEY_PIN);
136 | // Corresponds to 76.5
137 | int message[] = {AlzaET1Ng::RESPONSE_HEADER, 0x6d, 0x40, 0x5b, 0x10, 0x18};
138 | SetSerialMessage(message, AlzaET1Ng::RESPONSE_SIZE);
139 | for (int i=0; i < AlzaET1Ng::RESPONSE_SIZE; i++) {
140 | AlzaControl.handleIncomingResponse();
141 | }
142 |
143 | ASSERT_EQ(AlzaControl.getHeight(), 0);
144 |
145 | char str_data[4];
146 | AlzaControl.getBcdDisplayAsString(str_data);
147 | ASSERT_EQ(str_data[0], 'S');
148 | ASSERT_EQ(str_data[1], '-');
149 | ASSERT_EQ(str_data[2], '2');
150 | }
151 |
152 | int main(int argc, char* argv[]) {
153 | ::testing::InitGoogleTest(&argc, argv);
154 | return RUN_ALL_TESTS();
155 | }
--------------------------------------------------------------------------------
/tests/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.8)
2 | project(alzaergo-et-ng-tests)
3 | find_package(Threads REQUIRED)
4 |
5 | SET(CMAKE_CXX_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage")
6 |
7 | add_subdirectory(arduino_mock)
8 |
9 | include_directories(
10 | ${ARDUINO_MOCK_INCLUDE_DIRS}
11 | ${ARDUINO_MOCK_LIBS_DIR}/_deps/googletest-src/googletest/include
12 | ${ARDUINO_MOCK_LIBS_DIR}/_deps/googletest-src/googlemock/include
13 | )
14 |
15 | message("libs_dir: ${ARDUINO_MOCK_LIBS_DIR}")
16 |
17 | file(GLOB LIB_SRCS "../src/*.cpp")
18 | file(GLOB SRCS "*.cpp")
19 | add_executable(test-all ${SRCS} ${LIB_SRCS})
20 |
21 |
22 | target_link_libraries(test-all
23 | ${ARDUINO_MOCK_LIBS_DIR}/lib/libgtest.a
24 | ${ARDUINO_MOCK_LIBS_DIR}/lib/libgmock.a
25 | ${ARDUINO_MOCK_LIBS_DIR}/dist/lib/libarduino_mock.a
26 | ${CMAKE_THREAD_LIBS_INIT}
27 | )
28 |
29 | add_dependencies(test-all arduino_mock)
30 |
31 | enable_testing()
32 | add_test(TestAll test-all)
33 |
--------------------------------------------------------------------------------
/tests/arduino_mock/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.8)
2 | project(arduino_mock_builder C CXX)
3 | include(ExternalProject)
4 |
5 | ExternalProject_Add(arduino_mock
6 | GIT_REPOSITORY https://github.com/mmrazik/arduino-mock.git
7 | GIT_TAG hardware-serial
8 | PREFIX ${CMAKE_CURRENT_BINARY_DIR}/arduino_mock
9 | INSTALL_COMMAND ""
10 | )
11 |
12 | ExternalProject_Get_Property(arduino_mock source_dir)
13 | set(ARDUINO_MOCK_INCLUDE_DIRS ${source_dir}/include PARENT_SCOPE)
14 |
15 | ExternalProject_Get_Property(arduino_mock binary_dir)
16 | set(ARDUINO_MOCK_LIBS_DIR ${binary_dir} PARENT_SCOPE)
--------------------------------------------------------------------------------