From cfd8d4c01846c71ee2be26ce5a0c687357542cf0 Mon Sep 17 00:00:00 2001 From: Denis Denisov Date: Sun, 12 Sep 2021 16:01:26 +0300 Subject: [PATCH] Different types of units. Writing values, relay unit --- esp_server/ModbusServer.cs | 26 ++++++ esp_server/Program.cs | 2 + esp_server/Properties/launchSettings.json | 8 ++ esp_server/RegistersContainer.cs | 20 +++++ esp_server/Unit/RelayUnit.cs | 35 ++++++++ esp_server/Unit/Unit.cs | 85 ++++++++++++++++++++ esp_server/Unit/UnitFactory.cs | 36 +++++++++ esp_server/UnitDataWriter.cs | 55 +++++++++++++ esp_server/UnitsDatabase.cs | 97 ++++++----------------- readme.md | 6 +- 10 files changed, 296 insertions(+), 74 deletions(-) create mode 100644 esp_server/Properties/launchSettings.json create mode 100644 esp_server/Unit/RelayUnit.cs create mode 100644 esp_server/Unit/Unit.cs create mode 100644 esp_server/Unit/UnitFactory.cs create mode 100644 esp_server/UnitDataWriter.cs diff --git a/esp_server/ModbusServer.cs b/esp_server/ModbusServer.cs index 9c5a963..6a0c60b 100644 --- a/esp_server/ModbusServer.cs +++ b/esp_server/ModbusServer.cs @@ -111,6 +111,32 @@ namespace ESPServer response.Add((byte)(value & 0xFF)); } } + else if (function == 0x06) + { + int addr = (((int)received[8] << 8) | (int)received[9]); + int value = (((int)received[10] << 8) | (int)received[11]); + registers.WriteRegister(addr, value); + response.Add((byte)((addr >> 8) & 0xFF)); + response.Add((byte)(addr & 0xFF)); + response.Add((byte)((value >> 8) & 0xFF)); + response.Add((byte)(value & 0xFF)); + } + else if (function == 0x10) + { + int addr = (((int)received[8] << 8) | (int)received[9]); + int amount = (((int)received[10] << 8) | (int)received[11]); + // received[12] is amount of data bytes (2 * amount) + List values = new List(); + for (int a = 0; a < amount; a++) + { + values.Add(((int)received[13 + 2 * a] << 8) | (int)received[14 + 2 * a]); + } + registers.WriteRegisters(addr, values.ToArray()); + response.Add((byte)((addr >> 8) & 0xFF)); + response.Add((byte)(addr & 0xFF)); + response.Add((byte)((amount >> 8) & 0xFF)); + response.Add((byte)(amount & 0xFF)); + } else { throw new Exception("Unsupported function: " + function); diff --git a/esp_server/Program.cs b/esp_server/Program.cs index b9135af..c3cba25 100644 --- a/esp_server/Program.cs +++ b/esp_server/Program.cs @@ -62,6 +62,8 @@ namespace ESPServer unitReceiver.Start(); var unitPinger = new UnitPinger(db); unitPinger.Start(); + // initialize data writer (do not need to save it as it will react on events by itself) + new UnitDataWriter(db, registers); var modbusServer = new ModbusServer(registers); modbusServer.Port = config.RootElement.GetProperty("modbusPort").GetInt32(); modbusServer.Run(); diff --git a/esp_server/Properties/launchSettings.json b/esp_server/Properties/launchSettings.json new file mode 100644 index 0000000..ff8140d --- /dev/null +++ b/esp_server/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "esp_server": { + "commandName": "Project", + "commandLineArgs": "openhab-config" + } + } +} \ No newline at end of file diff --git a/esp_server/RegistersContainer.cs b/esp_server/RegistersContainer.cs index e19df38..421557c 100644 --- a/esp_server/RegistersContainer.cs +++ b/esp_server/RegistersContainer.cs @@ -7,6 +7,8 @@ namespace ESPServer { private Dictionary registers; + public event Action OnRegisterWritten; + public RegistersContainer() { registers = new Dictionary(); @@ -37,5 +39,23 @@ namespace ESPServer registers[address + 1] = (int)bytes[0] + (((int)bytes[1]) << 8); registers[address] = (int)bytes[2] + (((int)bytes[3]) << 8); } + + public void WriteRegister(int address, int value) + { + registers[address] = value; + OnRegisterWritten?.Invoke(address, value); + } + + public void WriteRegisters(int address, int[] values) + { + for (int i = 0; i < values.Length; i++) + { + registers[address + i] = values[i]; + } + for (int i = 0; i < values.Length; i++) + { + OnRegisterWritten?.Invoke(address + i, values[i]); + } + } } } diff --git a/esp_server/Unit/RelayUnit.cs b/esp_server/Unit/RelayUnit.cs new file mode 100644 index 0000000..0ead545 --- /dev/null +++ b/esp_server/Unit/RelayUnit.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ESPServer +{ + class RelayUnit : Unit + { + public override byte[] GetWriteCommand(int address, float value) + { + foreach (var v in values) + { + if (v.Value.name == "r" && v.Value.addressOffset + baseAddress == address) + { + return GetWritePayload(value); + } + } + return null; + } + + private byte[] GetWritePayload(float value) + { + string t = "r_write\x01"; + if (MathF.Abs(value) < 1e-4) t += "0"; + else t += "1"; + t += "\x01"; + return Encoding.ASCII.GetBytes(t); + } + + public override bool IsValueWritable(UnitValue v) + { + return v.name == "r"; + } + } +} diff --git a/esp_server/Unit/Unit.cs b/esp_server/Unit/Unit.cs new file mode 100644 index 0000000..a8947c9 --- /dev/null +++ b/esp_server/Unit/Unit.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ESPServer +{ + /* + * Описание параметра, который может придти от юнита + */ + class UnitValue + { + // Название параметра + public string name; + // Текущее значение + public float value; + // Сдвиг адреса, по которому это значение будет храниться в modbus + public int addressOffset; + } + + /* + * Описание конкретного модуля (релейный, датчик, whatever) + */ + class Unit + { + // Айдишник юнита (мак-адрес) + public string id; + // Название (для генерации конфигов например) + public string name; + // Базовый адрес в модбасе + public int baseAddress; + // Значения которые есть в юните + public Dictionary values; + // текущий IP юнита + public string ip; + + public UnitValue GetValue(string name) + { + if (!values.ContainsKey(name)) + { + return null; + } + return values[name]; + } + + public int MinOffset + { + get + { + int min = int.MaxValue; + foreach (var u in values) if (u.Value.addressOffset < min) min = u.Value.addressOffset; + return min; + } + } + + public int MaxOffset + { + get + { + int max = int.MinValue; + foreach (var u in values) if (u.Value.addressOffset > max) max = u.Value.addressOffset; + return max; + } + } + + public bool HasAddress(int address) + { + foreach (var u in values) + { + if (u.Value.addressOffset + baseAddress == address) return true; + } + return false; + } + + public virtual byte[] GetWriteCommand(int address, float value) + { + return null; + } + + public virtual bool IsValueWritable(UnitValue v) + { + return false; + } + } + +} diff --git a/esp_server/Unit/UnitFactory.cs b/esp_server/Unit/UnitFactory.cs new file mode 100644 index 0000000..030ad68 --- /dev/null +++ b/esp_server/Unit/UnitFactory.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; + +namespace ESPServer +{ + static class UnitFactory + { + public static Unit CreateUnit(JsonElement config) + { + var type = ""; + JsonElement val; + if (config.TryGetProperty("type", out val)) + { + type = val.GetString(); + } + Unit u; + if (type == "relay") u = new RelayUnit(); + else u = new Unit(); + u.id = config.GetProperty("id").GetString(); + u.name = config.GetProperty("name").GetString(); + u.baseAddress = config.GetProperty("address").GetInt32(); + u.values = new Dictionary(); + foreach (var knownValue in config.GetProperty("values").EnumerateArray()) + { + var uv = new UnitValue(); + uv.name = knownValue.GetProperty("name").GetString(); + uv.addressOffset = knownValue.GetProperty("offset").GetInt32(); + u.values[uv.name] = uv; + } + + return u; + } + } +} diff --git a/esp_server/UnitDataWriter.cs b/esp_server/UnitDataWriter.cs new file mode 100644 index 0000000..11a0fe0 --- /dev/null +++ b/esp_server/UnitDataWriter.cs @@ -0,0 +1,55 @@ +using NLog; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using System.Threading; + +namespace ESPServer +{ + class UnitDataWriter + { + const int UNIT_TARGET_PORT = 7000; + + private readonly Logger logger = LogManager.GetCurrentClassLogger(); + + UnitsDatabase db; + + public UnitDataWriter(UnitsDatabase db, RegistersContainer registers) + { + this.db = db; + registers.OnRegisterWritten += OnRegisterWritten; + } + + private void OnRegisterWritten(int address, float value) + { + logger.Debug("Register written, address = " + address.ToString() + " value = " + value.ToString()); + var unit = db.GetUnit(address); + if (unit == null) + { + return; + } + var payload = unit.GetWriteCommand(address, value); + if (payload == null) + { + logger.Info("Write of address " + address + " is not supported by unit " + unit.id); + return; + } + try + { + using (var udpClient = new UdpClient()) + { + udpClient.Connect(unit.ip, UNIT_TARGET_PORT); + udpClient.Send(payload, payload.Length); + udpClient.Close(); + } + } + catch (Exception e) + { + logger.Error(e.Message); + } + } + } +} diff --git a/esp_server/UnitsDatabase.cs b/esp_server/UnitsDatabase.cs index d321d8d..c46ba63 100644 --- a/esp_server/UnitsDatabase.cs +++ b/esp_server/UnitsDatabase.cs @@ -5,65 +5,6 @@ using System.Text.Json; namespace ESPServer { - /* - * Описание параметра, который может придти от юнита - */ - class UnitValue - { - // Название параметра - public string name; - // Текущее значение - public float value; - // Сдвиг адреса, по которому это значение будет храниться в modbus - public int addressOffset; - } - - /* - * Описание конкретного модуля (релейный, датчик, whatever) - */ - class Unit - { - // Айдишник юнита (мак-адрес) - public string id; - // Название (для генерации конфигов например) - public string name; - // Базовый адрес в модбасе - public int baseAddress; - // Значения которые есть в юните - public Dictionary values; - // текущий IP юнита - public string ip; - - public UnitValue GetValue(string name) - { - if (!values.ContainsKey(name)) - { - return null; - } - return values[name]; - } - - public int MinOffset - { - get - { - int min = int.MaxValue; - foreach (var u in values) if (u.Value.addressOffset < min) min = u.Value.addressOffset; - return min; - } - } - - public int MaxOffset - { - get - { - int max = int.MinValue; - foreach (var u in values) if (u.Value.addressOffset > max) max = u.Value.addressOffset; - return max; - } - } - } - class UnitsDatabase { List units; @@ -73,19 +14,7 @@ namespace ESPServer units = new List(); foreach (var unitConfig in config.EnumerateArray()) { - var u = new Unit(); - u.id = unitConfig.GetProperty("id").GetString(); - u.name = unitConfig.GetProperty("name").GetString(); - u.baseAddress = unitConfig.GetProperty("address").GetInt32(); - u.values = new Dictionary(); - foreach (var knownValue in unitConfig.GetProperty("values").EnumerateArray()) - { - var uv = new UnitValue(); - uv.name = knownValue.GetProperty("name").GetString(); - uv.addressOffset = knownValue.GetProperty("offset").GetInt32(); - u.values[uv.name] = uv; - } - units.Add(u); + units.Add(UnitFactory.CreateUnit(unitConfig)); } } @@ -131,6 +60,23 @@ namespace ESPServer return null; } + /// + /// Retrieves unit by modbus address of any value in it + /// + /// + /// + public Unit GetUnit(int valueAddress) + { + lock (units) + { + foreach (var u in units) + { + if (u.HasAddress(valueAddress)) return u; + } + } + return null; + } + #region OpenHAB config generation internal void GenerateItemsConfig(StringBuilder config) { @@ -175,7 +121,12 @@ namespace ESPServer var v = uv.Value; config.Append("\t\tThing data "); config.Append(u.name + "_" + v.name); - config.AppendFormat("[readStart = \"{0}\", readValueType = \"float32\"]\n", u.baseAddress + v.addressOffset); + config.AppendFormat("[readStart = \"{0}\", readValueType = \"float32\"", u.baseAddress + v.addressOffset); + if (u.IsValueWritable(v)) + { + config.AppendFormat(", writeStart = \"{0}\", writeValueType = \"float32\", writeType=\"holding\"", u.baseAddress + v.addressOffset); + } + config.Append("]\n"); } } config.Append("\t}\n"); diff --git a/readme.md b/readme.md index e38f349..3182a4c 100644 --- a/readme.md +++ b/readme.md @@ -22,4 +22,8 @@ optional arguments, everything separated with character 0x01. Possible commands are: 1. ping - no arguments. Informs unit that server is still alive -and able to communicate \ No newline at end of file +and able to communicate + +2. r_write - one argument either '0' or '1'. Sets relay state +for unit (only for relay modules). Note that relay on relay modules +is normally opened, so '0' means turned on and '1' means turned off. \ No newline at end of file