Reverse Engineering Senville/Midea S1S2 Bus

Posting this in case anyone else is working on Senville SComms or similar inverter systems. I’ve confirmed CRC and frame structure and started mapping payload fields in each frame. Would love to compare notes if anyone else is digging into this.

:warning: Before You Touch Anything

A lot of these SComms setups carry mains voltage on or near the control boards. I’m lucky with how mine is configured — most people won’t be.
If you don’t know exactly what you’re doing, you can:

  • Seriously injure yourself
  • Burn your house down
  • Destroy a very expensive inverter/compressor

I’m not responsible for what you do with this information.
This will void your warranty.
This post is just documenting my own setup for educational purposes.

If you’re unsure at all — hire a professional.

The Project

I couldn’t find any documentation on SComms/RS485 for my Senville/Midea central air unit, so I decided to start sniffing the bus myself. Since this is a central air system where the air handler and heat pump have dedicated power feeds, the communication lines in my setup do not carry high voltage. I had the lines tested by a certified electrician before connecting anything.

I'm currently streaming raw hex frames through a Waveshare adapter into a Python script and SQLite database to better understand how the units communicate and react to different operating conditions.

The long-term goal is to bring this into Home Assistant to monitor real performance metrics — compressor frequency, EXV position, bus voltage, demand vs. actual capacity, runtime, etc. Not to control the system, but to gain visibility into how the inverter and air handler are actually operating.

Hardware & Setup

Interface: Waveshare RS485 to Ethernet
Baud Rate: 4800
Bit, Parity, Stop: 8N1
flow: none
transfer protocol: none

CRC-16/MODBUS Checksum

It turns out the checksum is just standard CRC-16/MODBUS. If the CRC doesn’t match, the frame is discarded.

Polynomial: 0x8005 (x16+x15+x2+1) 

Reversed Poly: 0xA001 

Initial Value: 0xFFFF 

Bit Order: Reflected (Least Significant Bit first) 

Calculation Steps:

Initialize a 16-bit register to 0xFFFF.

XOR the data byte with the lower 8 bits of the register.

Shift the register right one bit.

If the bit shifted out was 1, XOR the register with 0xA001.

Repeat the shift 8 times per byte.

When appending to the frame, transmit the low-order byte first.

Traffic Logs

The traffic shows a consistent cycle between the Inverter (0001) and the Air Handler (0100).

Timestamp Source Msg ID Raw Hex Frame
13:51:42.853 Inverter (0001) 20 A00001200C120F000077742604B5010001001D51
13:51:42.946 Air Handler (0100) 20 A00100200C11010F000000170F6C60190000E7B400

After logging traffic for a while, I noticed a consistent rotation pattern: Every exchange starts with a pair of ID 20 messages (Inverter ↔ Air Handler). After each ID 20 exchange, the system cycles through a single secondary message ID before returning to the next ID 20 sync.

The observed rotation is:

20 → 50 → 20 → 51 → 20 → 52 → 20 → 53 → 20 → 91

The data in ID 20 is updated every few hundred milliseconds, while the data in the 50-53 series is updated less frequently as the cycle rotates.

Example Frame Breakdown

[A0] [01 00 20] [0C] [........DATA........] [E7 B4]

  • A0 → Header
  • 01 00 20 → Frame ID
  • 0C → Payload Length
  • DATA → Sensor Payload
  • E7 B4 → CRC16-MODBUS

Based on observed bus traffic and correlating values with live system behavior, these are the fields I believe I’ve identified so far. They align consistently with operation, but I have not fully validated payload locations yet — so treat this as interpretation, not absolute mapping.

Indoor Unit (IDU)

  • Mode
  • Capacity demand
  • Setpoint
  • Blower speed
  • Room temperature
  • Coil temperature
  • Actual demand

Outdoor Unit (ODU)

  • Mode
  • Capacity demand
  • Coil temperature
  • Outdoor ambient temperature
  • Outdoor quarter-degree temperature value
  • Discharge temperature
  • Compressor frequency (Hz)
  • Fan speed (target and actual)
  • DC bus voltage (target and actual)
  • Inverter DC bus voltage
  • AC input voltage
  • Current draw (Amps)
  • EXV steps
  • Runtime minutes
  • Runtime hours

These values were derived through observation and calculated scaling, not official documentation. There is still room for interpretation error. If anyone else is working on SComms or similar Midea-based systems and has mapped additional fields, I’d be interested in comparing observations.

3 Likes

Would this assist with decoding and sending values for experimentation on your WaveShare device?

@IOT7712 Thanks for the suggestion. I did look at Protocol Wizard, but this bus doesn’t appear to follow standard Modbus.

While SComms frames use a Modbus-style CRC, the protocol itself is proprietary. Frames start with 0xA0, have dynamic lengths, and the payload structure doesn’t match Modbus registers.

Right now I’m sniffing the bus and parsing the frames with a Python script that validates the CRC and pushes decoded values into Home Assistant via MQTT Auto-Discovery. That automatically builds 30+ sensors without any YAML.

After graphing the data in Home Assistant, some interesting behavior started showing up.

Control loop behavior (PID)

These values appear to relate to the inverter’s control loop. You can see the error spike and the system respond with a step correction before stabilizing.

1 Like

Electronic Expansion Valve activity

This is the EXV position in steps from the outdoor board. The valve moves quickly during load changes and then settles once the system stabilizes. Notice that after 30 minutes of running at a low load, the EXV spikes simultaneously with the PID error. This is most likely the unit triggering an oil return cycle to pull oil back to the sump.

Still working on confirming the exact meaning of some fields, but the behavior lines up well with what you’d expect from an inverter system managing refrigerant flow.

If anyone has experience with SComms (S1S2) or similar inverter protocols, I’d love to compare notes.

Very cool! Any detail how you have mapped the various sensors into the payload data?

@NyxVale63 I hope this will help

1 Like

I’d love to give this a try at some point! Is it just as simple as hooking up an rs485 module like the other project? Wonder if you’d have faster adoption if it used ESPhome?

I have a Midea 24v interface on the indoors (with 3rd party coil), that connects S1/S2 to the outdoor unit. However when I sniffed these communications the protocol is quite different than what you were seeing weirdly, maybe it’s a more legacy one. It seems a fixed 18-byte frames with 0xA3/0x3A header/terminator and a two’s complement checksum in a peer-to-peer 8-frame cycle. It has less data points than what you got, for example no EXV position, runtime counters etc. Maybe this is what is used on the single wire S comms too…

@NyxVale63

I installed my unit last year in April. Senville R454B 36K Central air. I’m guessing yours is legacy and possibly a multi protocol bus. My assumption based on my boot up handshake with random 0xA6/0x6A header/terminator I haven’t looked into yet. See if you are able to find the firmware number at the ODU. I was able to do the “Outdoor Unit Point Check Function” and my system is on Firmware 50.

@CWood

Please don’t assume this is like the other project and just hook an RS485 module to the IDU/ODU line.

Do not connect to that main line unless you fully understand what’s on it first. This is not a simple “tap in and test it” situation. Depending on the unit, those lines may carry way higher voltage than people expect , and if you guess wrong, you can absolutely damage hardware.

The other project is built around a specific known 2-way communication/control method. This is not that , and it should not be treated like it is.

As for ESPHome, adoption rate really isn’t my focus. I already have this working on my setup. I’m just sharing what I built and what I’ve learned.

If you know what you’re looking at and want to experiment carefully, I’m happy to answer questions and share what I can.

Yeah it’s an R410A unit, Midea “Light Commercial” DLCLRA series 36K. So maybe it has an older protocol.

I really want to figure out the TestPort that the ODU has where the Dr Smart tool goes to, just haven’t had time. That clearly gives out more data as the Dr Smart tool shows EXV position etc.

I wanted to figure out the testport as well. My problem is I have already tried the testport without success. Maybe if I can buy one of those Dr Smart tools I could figure out how it talks to the ODU but I don’t even know if it would work with my heatpump. Please let me know if you are able to figure it out and get any kind of connecting.

I’m very interested in how you are capturing and decoding your units data. Do you see the same master slave back and forth frames or is it different. Is their anything weird about the room temp frame values and outdoor temp BS?. I think I have reached the point that I need to reconsider how I am capturing my units data.

I starting to think I have an understanding of why my room temp drops by 8.5C when I turn off my unit. I haven’t seen any documentation within my units manuals but its still a known safety protocol. “Off Mode Freeze Protection”

My best guess is instead of having room temp going down to 8.5C or 46F and trigger an emergency heat mode. My unit offsets the real temp by -8.5C and if off mode offset temp is below freezing 0C or 32F emergency heat mode is activated.

I still have no clue why the room temp byte skips value 110 and 113.

Sorry for the naming issue in the screen shot. That will be fixed. Blue line is thermostat data, yellow line is S1S2 decoding.

I originally assumed that 0001_52 Byte 10 and 11 were part of the PID but quickly changed it to IPM temps per actual documented data points. I don’t think that is the case here.

Notice how the ODU temp drops but the IPM temps stay strangely to consistent. Ive looked through many MFG manuals but cant find any reason why the IDU needs to know the IPM temperatures or if they are a just random data point pushed to the thermostat or any other BUS.

Does anybody have a manual on how exactly this comm bus works??? That would make it a little easier maybe.

If you’re working with Midea or any other branded Midea inverter systems and have additional frame captures, sensor mappings, or scaling corrections, ill be glad to work with you and share my input.

Time to get back to this project... I finally had some time and figured out a XYE connection that still allows the use of the wired controller the other day. I decided to merge S1S2 and XYE and decode everything. I have a feeling decoding XYE will be a little easier than S1S2. I'm hoping the already mostly documented XYE will help in figuring out the last S1S2 project bytes that I just cant figure out. SQLite has been a game changer for helping me decode so far but I'm gonna end up switching to InfluxDB for long term database solution.

This is a very basic look at my dual XYE S1S2 bus looks like. I think every frame possible is in the image with the exception of a very weird "0804000066000059" and "0804080000000013" responses from frames aac3 aac6 with HAHB connected and sniffing XYE, and any frames in HAHB. Oh and the A00100 A00001 "25" frames during boot-up of the S1S2 units. Ill end up programing one ESP32 with dual 485toTTL boards to sniff both buses when the need to salvage an ESP32 is needed.

One interesting note is now that my wired thermostat is connected with XYE I no longer have access to the performance calculations for the airflow and actual "power usage" in the native Senville app. This makes me want to look into why the app always have an idle power usage as EXV low position + 1 (0001_53_byte[11] + 1 )

If you're going to use XYE, there's a large thread about it here in case you missed it. Might be easier to build on what others have implemented rather than rolling your own.

@ShadowFist Thanks for the link. Ive definitely keep up with that thread and appreciate the work they have put into their XYE project. I was hoping that finally getting a XYE connection that allows me to also have access with the wired controller (because the wife wants it) I could figure out a few more S1S2 payload bytes. So far I think it did, but it seems its also be the other way around.

My XYE frame C0 Byte 28, my theory so far..

I think I need to figure out how to explain all of this but its not EEV (IDU), EXV (ODU) or fan speed that I have previous read. This stumped me until I realized its a multi-state byte based off of bit 7 that flips between High mode (In my ODU EXV 1024 steps +) Maintenance Mode for Oil Return and I'm assuming Defrost Mode and maybe others | Efficiency Mode | Idle mode and to look like a basic glitchy sensor. Freaking crazy genius.

I think this got me to my S1S2 project 0001_50_b17 IDU EEV open percentage. Yes I have a outdoor EXV and indoor EEV per all of my manuals and physical plugs... With the IDU and ODU EEV Efficiency levels and Maintenance override.


Note: EEV image is not the same day as XYE byte theory spreadsheet images.

This is just my working theory.

I wasn't totally incorrect... c0_b28 is not EEV... c0_b28 (EEV Low) and c0_b29 (EEV High) is the EEV Step position with respect to c0_b28 bit 7 being aligned with Low vs High demand and c0_b28 bit 0 being some kind of flag maybe. If you add EEV Low and High you get:

Max Open: 480
High Load: 384 - 480
Low Load: ? - 383
High Load Startup: 450 (Low 194, High 1)
Low Load Startup: 350 (Low 94, High 1)

In cool mode.
During startup Indoor EEV stays constant during ODU 6.5 min start cycle then for Cycle 1 the EEV does the "count up or down", 5 1.5 min cycles I guess to determine best step position followed by 5 3 min cycles... If C0_B19 is still active we do a 2 min oil return process.

Cycle 2: EEV does 10 3 min step adjustments and If C0_B19 is still active we do a 2 min oil return process.

Rinse and repeat Cycle 2 until...

In the first image rows 28 and 29 gave it away. How can you have a counter go negative. I found its neighbor.

The rest feel in place.

Here's what a typical startup looks like. I'm not sure what actually determines the 94 or 194 but that's the 350 vs 450 startup steps

WTH... Is this a thing to... How can we not know the unit is active vs idle, or maybe this just relates to my unite... c0_b19

Thanks for sharing - this is a very interesting project.

My MRCOOL 12K mini-split uses 4 wires to connect the indoor with the outdoor unit. One is hot, one is ground. I've traced the other two with my oscilloscope:

One carries a signal, the other stays around 0 volts.

Does this look like RS485 to you? I've bought the waveshare dongle, but

netcat 192.168.0.7 8888

doesn't spit out anything.

This is the output from your script:

./venv/bin/python3 -m src.main --ip 192.168.0.7 --port 8888
🚫 MQTT disabled
🚫 DB disabled
🦅 ENGAGE | 192.168.0.7:8888
🔗 TCP CONNECTION ESTABLISHED

I tried A+ (yellow trace) and B- (blue trace) and vice versa. No luck. The dongle does not spit out any data.

My configuration:

  • Baud Rate: 4800
  • Data Size: 8 bit
  • Parity: None
  • Stop Bits: 1 bit
  • Local Port Number: 8888
  • Work Mode: TCP Server
  • RESET:
  • LINK:
  • INDEX:
  • Similar RFC2217: