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

Multiple outdoor units #24

Open
sodabrew opened this issue Nov 19, 2022 · 11 comments
Open

Multiple outdoor units #24

sodabrew opened this issue Nov 19, 2022 · 11 comments

Comments

@sodabrew
Copy link

sodabrew commented Nov 19, 2022

I installed two CX34 units. At a high level, I can see two options for talking to them:

  1. Two RS-485 busses, each machine is ID 1 on its own bus, run two copies of cx34collector.

    • cx34dash would be best with one web interface and either overlaid or side-by-side charts so any differences can be quickly observed. If there are two collector instances this means a list of grpc ports to talk to.
  2. One RS-485 bus, change the ID of one of the machines (it's a setting on the touch screen controller), modify cx34collector to accept a list of bus IDs to collect from.

    • Reading the Connect method in cx34.go, the slaveId variable seems bound up with the serial port instance. Would need to separate these so that one serial port instance is used by multiple modbus instances ... making sure frames aren't clobbered on the wire at the same time.

Other thoughts:

  • Probably should have separate database files per machine, yeah?
@gonzojive
Copy link
Owner

gonzojive commented Nov 19, 2022

2 would be best. Some sort of configuration file that specifies what devices are available and what serial device should be used to connect to them sounds appropriate. The same config file could be used for the fan coil units, too. It'd be good to also assign a UUID to each device. AFAIK there is no serial number info available through modbus commands.

// config.proto

message Config {
  repeated HeatPump heat_pumps = 1;
  repeated FanCoilUnit fan_coil_units = 2;
}

message HeatPump {
  // Address of the device.
  ModbusServerSpec address = 1;
}

message FanCoilUnit {
  // Address of the device.
  ModbusServerSpec address = 1;
}

message ModbusServerSpec {
  // Modbus address of the device. 2 bytes.
  uint32 server_id = 1;

  // Serial device used to connect to the server. Value of this ifeld is something like "/dev/ttyX"
  string serial_device= 2;
}

I plan to remove the offensive terms from the library at some point (replace slaveID with serverID).

@sodabrew
Copy link
Author

sodabrew commented Nov 20, 2022

+1. Just confirming that I understand: in the Modbus model, the device is the server and this software on the RPi is the client?

Is the protobuf text format sufficiently human-readable for a config file? I've never used it as such, but if you want to stick to gRPC I'm not about to roll in with a PR to make everything JSON. https://developers.google.com/protocol-buffers/docs/text-format-spec

@gonzojive
Copy link
Owner

+1. Just confirming that I understand: in the Modbus model, the device is the server and this software on the RPi is the client?

Yes, that's my understanding of the how it works.

prototext is the Go library for the text format, and it's decently readable. What's nice vs. JSON is there is more validation. https://marketplace.visualstudio.com/items?itemName=thesofakillers.vscode-pbtxt

Protos have a JSON encoding that would allow storing the config in JSON or YAML, but proto text format is simpler.

@sodabrew
Copy link
Author

Agreed, protobuf-json format is rough. I'll take a look at proto-text.

@sodabrew
Copy link
Author

sodabrew commented Nov 27, 2022

Something that's not totally clear to me: can you run the fancoil client on the same RS-485 device as the cx34 client? Each sets up its own instance of handler := modbus.NewRTUClientHandler(p.TTYDevice) -- is there an underlying locking mechanism that prevents the two from clobbering each other on the same serial bus?

Looking around at the modbus implementations in Go (as I'm sure you did! but I'm getting myself up to speed...) I found one with an explicit method of switching device id:

https://github.com/simonvetter/modbus

    client.SetUnitId(4)

The goburrow library in use here has an awkward reach into the handler structure to change the id and hasn't followed up on PRs to improve in recent years:
goburrow/modbus#36

if handler, ok := q.handler.(*modbus.RTUClientHandler); ok {
    handler.SlaveId = deviceid
} else if handler, ok := q.handler.(*modbus.TCPClientHandler); ok {
    handler.SlaveId = deviceid
}

It looks like there is a fork of goburrow/modbus that has adopted this PR:
grid-x/modbus#8

    handler.SetSlave(4)

There is a totally different proposal to move the id into each method call:
goburrow/modbus#87 e.g.

-	ReadCoils(address, quantity uint16) (results []byte, err error)
+	ReadCoils(slaveId uint8, address, quantity uint16) (results []byte, err error)

And a very minimalist implementation that follows the same pattern of id in each method call: https://github.com/dpapathanasiou/go-modbus (IMO this implementation too minimalist, leaving most of the modbus parsing to the calling code - but maybe that's helpful for Omron protocol!? -- anyways, the device id in method call idea is what I was looking for)

Only https://github.com/simonvetter/modbus has taken up Modbus org's offensive terms changes, albeit using yet a different naming ("unit id" vs. "server id" -- I'll avoid the tangent where I argue into the wind that I, a random person on the Internet who started using Modbus a few days ago but has been writing network software for a couple of decades, boldly proclaims that the words "client/server" make no sense in this context and something like "controller/device" would be way more sensible! oops)

@gonzojive
Copy link
Owner

Something that's not totally clear to me: can you run the fancoil client on the same RS-485 device as the cx34 client? Each sets up its own instance of handler := modbus.NewRTUClientHandler(p.TTYDevice) -- is there an underlying locking mechanism that prevents the two from clobbering each other on the same serial bus?

I'm not sure. I don't think there is any locking mechanism and suspect using the same device would be unsafe. Both Go processes would need to be able to read the same bytes from the same device to process traffic on the bus. Writes would also require locking.

I was a little paranoid myself and bought two USB RS485 dongles just in case... one for the fan coils and one for the heat pump.

--

Using a different modbus library would be fine with me if upstream isn't accepting patches.

@gonzojive
Copy link
Owner

It looks like serial port locking comes up and two processes will cause problems.
https://stackoverflow.com/questions/17980725/locking-linux-serial-port/17983721#17983721

One software solution is to update this code to use a single process for reading/writing from the serial device. Another is probably closing the serial device when not in use and protecting opens with a lock. I would just grab another USB to RS485 converter for now. The Waveshare ones seem pretty high quality and are ~$20.

@njmckenzie
Copy link

FYI that I got some really weird behavior using a single RS485 even with a single process (I think). I'm using the logic from this project but putting it in OpenHab instead of Google. I'm not 100% sure the openHab Modbus code is actually using a single process but it was easy rationale to stick with two RS-485 devices. When I polled certain addresses (no discernible pattern) on the CX34, it would cause all the fan coils to start blinking gibberish and stop working. The instant I deactivated the CX34 polling, they returned to normal.

@sodabrew
Copy link
Author

What other devices were on your RS-485 bus? In theory, one bus could have many different components from different vendors; but I wonder if building automation folks know that in practice there are known issues?

It's been a couple of months since I had time to hack on this, but I think the path to using the same bus will be factoring out a "bus manager" from the other components of the system. The more I sketched my idea, the more I realized I was reinventing Modbus over TCP -- that is, the "bus manager" would sit on the serial port and expose a TCP port and client programs would use Modbus over TCP. That seems to be where the industry is, definitely feel like the hobbyist playing catch-up :)

This codebase mentions using Omron format. I wasn't able to find any descriptions on the web of how that interacts with Modbus over TCP.

@gonzojive
Copy link
Owner

gonzojive commented Jan 17, 2023 via email

@sodabrew
Copy link
Author

sodabrew commented Apr 27, 2023

It's finally spring season, which means switching between heating and cooling modes every few days. Time to dust off my Pi and get this working! I tinkered with the collector but realized I wanted a more minimal starting point. So I started ripping down to the bare bones. I have a small command line output now. I haven't committed to my fork yet.

The mode and target temperatures are the things I need to be able to change remotely, so I'll work on adding write support to those registers from the command line. Here's the first light I have so far:

~/chilcmd $ ./chilcmd -tty /dev/ttyUSB0 -unit 2
Summary for CX34 unit 2:
  Mode: Cooling + DHW
  COP: 0.00 (stopped)
  Power: 0.00 Watts
  Outdoor Temp: 55.04 °F
  Active Target Temp: 87.80 °F
  Heating Target Temp: 87.80 °F
  Inlet Temp: 55.76 °F
  Outlet Temp: 55.40 °F
  Pump Speed: 0.01 l/s
  Useful Heat Rate: 0.1163kg/s * -0.2°K * 4kJ/(kg * °K) = -93J/s = -0.09kW

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants