somfy_sdn 2.1.5 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/somfy_sdn +69 -0
- data/lib/sdn/cli/mqtt/group.rb +12 -10
- data/lib/sdn/cli/mqtt/motor.rb +19 -14
- data/lib/sdn/cli/mqtt/p_queue.rb +18 -0
- data/lib/sdn/cli/mqtt/read.rb +125 -126
- data/lib/sdn/cli/mqtt/subscriptions.rb +186 -140
- data/lib/sdn/cli/mqtt/write.rb +39 -34
- data/lib/sdn/cli/mqtt.rb +84 -53
- data/lib/sdn/cli/provisioner.rb +56 -33
- data/lib/sdn/cli/simulator.rb +99 -65
- data/lib/sdn/client.rb +38 -24
- data/lib/sdn/message/control.rb +60 -30
- data/lib/sdn/message/get.rb +6 -2
- data/lib/sdn/message/helpers.rb +23 -22
- data/lib/sdn/message/ilt2/get.rb +6 -3
- data/lib/sdn/message/ilt2/master_control.rb +10 -7
- data/lib/sdn/message/ilt2/post.rb +7 -5
- data/lib/sdn/message/ilt2/set.rb +28 -19
- data/lib/sdn/message/post.rb +3 -5
- data/lib/sdn/message/set.rb +48 -22
- data/lib/sdn/message.rb +50 -34
- data/lib/sdn/version.rb +3 -1
- data/lib/sdn.rb +18 -12
- data/lib/somfy_sdn.rb +3 -1
- metadata +43 -13
- data/bin/somfy_sdn +0 -60
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a00033a8ac556f5b578c6d13b678b54393376c1f0439340dbd4afbd4a2376714
         | 
| 4 | 
            +
              data.tar.gz: 1925c30fa53a68f0eb388b0ff277a00a0ae40bf2798a93ba8e9b955139b2c989
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ce9dc75cac82bd65e4fd89908f6e6563761c5171c8dc93ab5b0e40fbe0ce0e0bb1dad7d90ef88470d90a050e7893fb1a35edb589ef2881886bf451d5962a6599
         | 
| 7 | 
            +
              data.tar.gz: b82d6d1d60509009c7ea7b7501bd9790ae16f295185cdd5c125e10754e3a96ab9ef9d8956da8d4af554f88390eb4faea2eb4bfb7d0bb7e71248c23e043bc0338
         | 
    
        data/exe/somfy_sdn
    ADDED
    
    | @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require "somfy_sdn"
         | 
| 5 | 
            +
            require "thor"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class SomfySDNCLI < Thor
         | 
| 8 | 
            +
              class_option :verbose, type: :boolean, default: false, desc: "Log protocol messages"
         | 
| 9 | 
            +
              class_option :trace, type: :boolean, default: false, desc: "Log protocol bytes"
         | 
| 10 | 
            +
              class_option :log, type: :string, desc: "Log to a file"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              desc "monitor PORT", "Monitor traffic on the SDN network at PORT"
         | 
| 13 | 
            +
              def monitor(port)
         | 
| 14 | 
            +
                sdn = handle_global_options(port)
         | 
| 15 | 
            +
                SDN.logger.level = :debug
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                loop do
         | 
| 18 | 
            +
                  sdn.receive do |message|
         | 
| 19 | 
            +
                    # do nothing
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              desc "mqtt PORT MQTT_URI", "Run an MQTT bridge to control the SDN network at PORT"
         | 
| 25 | 
            +
              option :"device-id", default: "somfy", desc: "The Homie Device ID"
         | 
| 26 | 
            +
              option :"base-topic", default: "homie", desc: "The base Homie topic"
         | 
| 27 | 
            +
              option :"auto-discover", type: :boolean, default: true, desc: "Do a discovery at startup"
         | 
| 28 | 
            +
              option :address, type: :array, desc: "Specify a known motor address to speed discovery"
         | 
| 29 | 
            +
              def mqtt(port, mqtt_uri)
         | 
| 30 | 
            +
                sdn = handle_global_options(port)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                require "sdn/cli/mqtt"
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                SDN::CLI::MQTT.new(sdn,
         | 
| 35 | 
            +
                                   mqtt_uri,
         | 
| 36 | 
            +
                                   device_id: options["device-id"],
         | 
| 37 | 
            +
                                   base_topic: options["base-topic"],
         | 
| 38 | 
            +
                                   auto_discover: options["auto-discover"],
         | 
| 39 | 
            +
                                   known_motors: options["address"])
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              desc "provision PORT [ADDRESS]", "Provision a motor (label and set limits) at PORT"
         | 
| 43 | 
            +
              def provision(port, address = nil)
         | 
| 44 | 
            +
                sdn = handle_global_options(port)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                require "sdn/cli/provisioner"
         | 
| 47 | 
            +
                SDN::CLI::Provisioner.new(sdn, address)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              desc "simulator PORT [ADDRESS]", "Simulate a motor (for debugging purposes) at PORT"
         | 
| 51 | 
            +
              def simulator(port, address = nil)
         | 
| 52 | 
            +
                sdn = handle_global_options(port)
         | 
| 53 | 
            +
                SDN.logger.level = :debug
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                require "sdn/cli/simulator"
         | 
| 56 | 
            +
                SDN::CLI::Simulator.new(sdn, address)
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              private
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def handle_global_options(port)
         | 
| 62 | 
            +
                SDN.logger = Logger.new(options[:log]) if options[:log]
         | 
| 63 | 
            +
                SDN.logger.level = options[:verbose] || options[:trace] ? :debug : :info
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                SDN::Client.new(port).tap { |sdn| sdn.trace = options[:trace] }
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            SomfySDNCLI.start(ARGV)
         | 
    
        data/lib/sdn/cli/mqtt/group.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module SDN
         | 
| 2 4 | 
             
              module CLI
         | 
| 3 5 | 
             
                class MQTT
         | 
| @@ -6,24 +8,24 @@ module SDN | |
| 6 8 | 
             
                      members.each { |k| self[k] = :nil }
         | 
| 7 9 | 
             
                      super
         | 
| 8 10 | 
             
                    end
         | 
| 9 | 
            -
             | 
| 11 | 
            +
             | 
| 10 12 | 
             
                    def publish(attribute, value)
         | 
| 11 | 
            -
                       | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                       | 
| 13 | 
            +
                      return unless self[attribute] != value
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                      bridge.publish("#{addr}/#{attribute.to_s.tr("_", "-")}", value.to_s)
         | 
| 16 | 
            +
                      self[attribute] = value
         | 
| 15 17 | 
             
                    end
         | 
| 16 | 
            -
             | 
| 18 | 
            +
             | 
| 17 19 | 
             
                    def printed_addr
         | 
| 18 20 | 
             
                      Message.print_address(Message.parse_address(addr))
         | 
| 19 21 | 
             
                    end
         | 
| 20 | 
            -
             | 
| 22 | 
            +
             | 
| 21 23 | 
             
                    def motor_objects
         | 
| 22 | 
            -
                      bridge.motors.select { | | 
| 24 | 
            +
                      bridge.motors.select { |_addr, motor| motor.groups_string.include?(printed_addr) }.values
         | 
| 23 25 | 
             
                    end
         | 
| 24 | 
            -
             | 
| 26 | 
            +
             | 
| 25 27 | 
             
                    def motors_string
         | 
| 26 | 
            -
                      motor_objects.map { |m| Message.print_address(Message.parse_address(m.addr)) }.sort.join( | 
| 28 | 
            +
                      motor_objects.map { |m| Message.print_address(Message.parse_address(m.addr)) }.sort.join(",")
         | 
| 27 29 | 
             
                    end
         | 
| 28 30 | 
             
                  end
         | 
| 29 31 | 
             
                end
         | 
    
        data/lib/sdn/cli/mqtt/motor.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module SDN
         | 
| 2 4 | 
             
              module CLI
         | 
| 3 5 | 
             
                class MQTT
         | 
| @@ -58,14 +60,14 @@ module SDN | |
| 58 60 | 
             
                      @groups = [].fill(nil, 0, 16)
         | 
| 59 61 | 
             
                      super
         | 
| 60 62 | 
             
                    end
         | 
| 61 | 
            -
             | 
| 63 | 
            +
             | 
| 62 64 | 
             
                    def publish(attribute, value)
         | 
| 63 | 
            -
                       | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
                       | 
| 65 | 
            +
                      return unless self[attribute] != value
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                      bridge.publish("#{addr}/#{attribute.to_s.tr("_", "-")}", value.to_s)
         | 
| 68 | 
            +
                      self[attribute] = value
         | 
| 67 69 | 
             
                    end
         | 
| 68 | 
            -
             | 
| 70 | 
            +
             | 
| 69 71 | 
             
                    def add_group(index, address)
         | 
| 70 72 | 
             
                      group = bridge.add_group(Message.print_address(address)) if address
         | 
| 71 73 | 
             
                      old_group = @groups[index - 1]
         | 
| @@ -74,10 +76,13 @@ module SDN | |
| 74 76 | 
             
                      publish(:groups, groups_string)
         | 
| 75 77 | 
             
                      bridge.touch_group(old_group) if old_group
         | 
| 76 78 | 
             
                    end
         | 
| 77 | 
            -
             | 
| 78 | 
            -
                    def set_groups(groups)
         | 
| 79 | 
            -
                      return unless  | 
| 80 | 
            -
             | 
| 79 | 
            +
             | 
| 80 | 
            +
                    def set_groups(groups) # rubocop:disable Naming/AccessorMethodName
         | 
| 81 | 
            +
                      return unless /^(?:\h{2}[:.]?\h{2}[:.]?\h{2}(?:,\h{2}[:.]?\h{2}[:.]?\h{2})*)?$/i.match?(groups)
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                      groups = groups.split(",").sort.uniq.map do |g|
         | 
| 84 | 
            +
                                 Message.parse_address(g)
         | 
| 85 | 
            +
                               end.select { |g| Message.group_address?(g) }
         | 
| 81 86 | 
             
                      groups.fill(nil, groups.length, 16 - groups.length)
         | 
| 82 87 | 
             
                      messages = []
         | 
| 83 88 | 
             
                      sdn_addr = Message.parse_address(addr)
         | 
| @@ -89,13 +94,13 @@ module SDN | |
| 89 94 | 
             
                      end
         | 
| 90 95 | 
             
                      messages
         | 
| 91 96 | 
             
                    end
         | 
| 92 | 
            -
             | 
| 97 | 
            +
             | 
| 93 98 | 
             
                    def groups_string
         | 
| 94 | 
            -
                      @groups.compact.map { |g| Message.print_address(g) }.sort.uniq.join( | 
| 99 | 
            +
                      @groups.compact.map { |g| Message.print_address(g) }.sort.uniq.join(",")
         | 
| 95 100 | 
             
                    end
         | 
| 96 | 
            -
             | 
| 101 | 
            +
             | 
| 97 102 | 
             
                    def group_objects
         | 
| 98 | 
            -
                      groups_string.split( | 
| 103 | 
            +
                      groups_string.split(",").map { |addr| bridge.groups[addr.delete(".")] }
         | 
| 99 104 | 
             
                    end
         | 
| 100 105 | 
             
                  end
         | 
| 101 106 | 
             
                end
         | 
    
        data/lib/sdn/cli/mqtt/read.rb
    CHANGED
    
    | @@ -1,151 +1,150 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module SDN
         | 
| 2 4 | 
             
              module CLI
         | 
| 3 5 | 
             
                class MQTT
         | 
| 4 6 | 
             
                  module Read
         | 
| 5 7 | 
             
                    def read
         | 
| 6 8 | 
             
                      loop do
         | 
| 7 | 
            -
                         | 
| 8 | 
            -
                          @ | 
| 9 | 
            -
                             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                               | 
| 13 | 
            -
                               | 
| 14 | 
            -
                               | 
| 15 | 
            -
             | 
| 16 | 
            -
                                @motors_found = true
         | 
| 17 | 
            -
                                motor = publish_motor(src.gsub('.', ''), message.node_type)
         | 
| 18 | 
            -
                              end
         | 
| 9 | 
            +
                        @sdn.receive do |message|
         | 
| 10 | 
            +
                          @mqtt.batch_publish do
         | 
| 11 | 
            +
                            src = Message.print_address(message.src)
         | 
| 12 | 
            +
                            # ignore the UAI Plus and ourselves
         | 
| 13 | 
            +
                            if src != "7F.7F.7F" && !Message.group_address?(message.src) && !(motor = @motors[src.delete(".")])
         | 
| 14 | 
            +
                              SDN.logger.info "Found new motor #{src}"
         | 
| 15 | 
            +
                              @motors_found = true
         | 
| 16 | 
            +
                              motor = publish_motor(src.delete("."), message.node_type)
         | 
| 17 | 
            +
                            end
         | 
| 19 18 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                               | 
| 27 | 
            -
                                 | 
| 28 | 
            -
                                if message. | 
| 29 | 
            -
             | 
| 30 | 
            -
                                   | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
                                   | 
| 34 | 
            -
                                    motor.publish(: | 
| 35 | 
            -
             | 
| 36 | 
            -
                                      motor.publish(:last_direction, motor.position_pulses < message.position_pulses ? :down : :up)
         | 
| 37 | 
            -
                                    end
         | 
| 38 | 
            -
                                    follow_ups << Message::ILT2::GetMotorPosition.new(message.src)
         | 
| 39 | 
            -
                                  end
         | 
| 40 | 
            -
                                  motor.last_position_pulses = motor.position_pulses
         | 
| 41 | 
            -
                                  ip = (1..16).find do |i|
         | 
| 42 | 
            -
                                    # divide by 5 for some leniency
         | 
| 43 | 
            -
                                    motor["ip#{i}_pulses"].to_i / 5 == message.position_pulses / 5
         | 
| 19 | 
            +
                            follow_ups = []
         | 
| 20 | 
            +
                            case message
         | 
| 21 | 
            +
                            when Message::PostNodeLabel
         | 
| 22 | 
            +
                              publish("#{motor.addr}/$name", message.label) if motor.publish(:label, message.label)
         | 
| 23 | 
            +
                            when Message::PostMotorPosition,
         | 
| 24 | 
            +
                              Message::ILT2::PostMotorPosition
         | 
| 25 | 
            +
                              if message.is_a?(Message::ILT2::PostMotorPosition)
         | 
| 26 | 
            +
                                # keep polling while it's still moving; check prior two positions
         | 
| 27 | 
            +
                                if motor.position_pulses == message.position_pulses &&
         | 
| 28 | 
            +
                                   motor.last_position_pulses == message.position_pulses
         | 
| 29 | 
            +
                                  motor.publish(:state, :stopped)
         | 
| 30 | 
            +
                                else
         | 
| 31 | 
            +
                                  motor.publish(:state, :running)
         | 
| 32 | 
            +
                                  if motor.position_pulses && motor.position_pulses != message.position_pulses
         | 
| 33 | 
            +
                                    motor.publish(:last_direction,
         | 
| 34 | 
            +
                                                  (motor.position_pulses < message.position_pulses) ? :down : :up)
         | 
| 44 35 | 
             
                                  end
         | 
| 45 | 
            -
                                   | 
| 36 | 
            +
                                  follow_ups << Message::ILT2::GetMotorPosition.new(message.src)
         | 
| 46 37 | 
             
                                end
         | 
| 47 | 
            -
                                motor. | 
| 48 | 
            -
                                 | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 38 | 
            +
                                motor.last_position_pulses = motor.position_pulses
         | 
| 39 | 
            +
                                ip = (1..16).find do |i|
         | 
| 40 | 
            +
                                  # divide by 5 for some leniency
         | 
| 41 | 
            +
                                  motor["ip#{i}_pulses"].to_i / 5 == message.position_pulses / 5
         | 
| 42 | 
            +
                                end
         | 
| 43 | 
            +
                                motor.publish(:ip, ip)
         | 
| 44 | 
            +
                              end
         | 
| 45 | 
            +
                              motor.publish(:position_percent, message.position_percent)
         | 
| 46 | 
            +
                              motor.publish(:position_pulses, message.position_pulses)
         | 
| 47 | 
            +
                              motor.publish(:ip, message.ip) if message.respond_to?(:ip)
         | 
| 48 | 
            +
                              motor.group_objects.each do |group|
         | 
| 49 | 
            +
                                positions_percent = group.motor_objects.map(&:position_percent)
         | 
| 50 | 
            +
                                positions_pulses = group.motor_objects.map(&:position_pulses)
         | 
| 51 | 
            +
                                ips = group.motor_objects.map(&:ip)
         | 
| 54 52 |  | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 53 | 
            +
                                position_percent = nil
         | 
| 54 | 
            +
                                # calculate an average, but only if we know a position for
         | 
| 55 | 
            +
                                # every shade
         | 
| 56 | 
            +
                                if !positions_percent.include?(:nil) && !positions_percent.include?(nil)
         | 
| 57 | 
            +
                                  position_percent = positions_percent.sum / positions_percent.length
         | 
| 58 | 
            +
                                end
         | 
| 61 59 |  | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 60 | 
            +
                                position_pulses = nil
         | 
| 61 | 
            +
                                if !positions_pulses.include?(:nil) && !positions_pulses.include?(nil)
         | 
| 62 | 
            +
                                  position_pulses = positions_pulses.sum / positions_pulses.length
         | 
| 63 | 
            +
                                end
         | 
| 66 64 |  | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 65 | 
            +
                                ip = nil
         | 
| 66 | 
            +
                                ip = ips.first if ips.uniq.length == 1
         | 
| 67 | 
            +
                                ip = nil if ip == :nil
         | 
| 70 68 |  | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 69 | 
            +
                                group.publish(:position_percent, position_percent)
         | 
| 70 | 
            +
                                group.publish(:position_pulses, position_pulses)
         | 
| 71 | 
            +
                                group.publish(:ip, ip)
         | 
| 72 | 
            +
                              end
         | 
| 73 | 
            +
                            when Message::PostMotorStatus
         | 
| 74 | 
            +
                              if message.state == :running || motor.state == :running ||
         | 
| 75 | 
            +
                                 # if it's explicitly stopped, but we didn't ask it to, it's probably
         | 
| 76 | 
            +
                                 # changing directions so keep querying
         | 
| 77 | 
            +
                                 (message.state == :stopped &&
         | 
| 78 | 
            +
                                   message.last_action_cause == :explicit_command &&
         | 
| 79 | 
            +
                                   !(motor.last_action == Message::Stop || motor.last_action.nil?))
         | 
| 80 | 
            +
                                follow_ups << Message::GetMotorStatus.new(message.src)
         | 
| 81 | 
            +
                              end
         | 
| 82 | 
            +
                              # this will do one more position request after it stopped
         | 
| 83 | 
            +
                              follow_ups << Message::GetMotorPosition.new(message.src)
         | 
| 84 | 
            +
                              motor.publish(:state, message.state)
         | 
| 85 | 
            +
                              motor.publish(:last_direction, message.last_direction)
         | 
| 86 | 
            +
                              motor.publish(:last_action_source, message.last_action_source)
         | 
| 87 | 
            +
                              motor.publish(:last_action_cause, message.last_action_cause)
         | 
| 88 | 
            +
                              motor.group_objects.each do |group|
         | 
| 89 | 
            +
                                states = group.motor_objects.map(&:state).uniq
         | 
| 90 | 
            +
                                state = (states.length == 1) ? states.first : "mixed"
         | 
| 91 | 
            +
                                group.publish(:state, state)
         | 
| 94 92 |  | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
                                end
         | 
| 118 | 
            -
                              when Message::PostGroupAddr
         | 
| 119 | 
            -
                                motor.add_group(message.group_index, message.group_address)
         | 
| 93 | 
            +
                                directions = group.motor_objects.map(&:last_direction).uniq
         | 
| 94 | 
            +
                                direction = (directions.length == 1) ? directions.first : "mixed"
         | 
| 95 | 
            +
                                group.publish(:last_direction, direction)
         | 
| 96 | 
            +
                              end
         | 
| 97 | 
            +
                            when Message::PostMotorLimits
         | 
| 98 | 
            +
                              motor.publish(:up_limit, message.up_limit)
         | 
| 99 | 
            +
                              motor.publish(:down_limit, message.down_limit)
         | 
| 100 | 
            +
                            when Message::ILT2::PostMotorSettings
         | 
| 101 | 
            +
                              motor.publish(:down_limit, message.limit)
         | 
| 102 | 
            +
                            when Message::PostMotorDirection
         | 
| 103 | 
            +
                              motor.publish(:direction, message.direction)
         | 
| 104 | 
            +
                            when Message::PostMotorRollingSpeed
         | 
| 105 | 
            +
                              motor.publish(:up_speed, message.up_speed)
         | 
| 106 | 
            +
                              motor.publish(:down_speed, message.down_speed)
         | 
| 107 | 
            +
                              motor.publish(:slow_speed, message.slow_speed)
         | 
| 108 | 
            +
                            when Message::PostMotorIP,
         | 
| 109 | 
            +
                              Message::ILT2::PostMotorIP
         | 
| 110 | 
            +
                              motor.publish(:"ip#{message.ip}_pulses", message.position_pulses)
         | 
| 111 | 
            +
                              if message.respond_to?(:position_percent)
         | 
| 112 | 
            +
                                motor.publish(:"ip#{message.ip}_percent", message.position_percent)
         | 
| 113 | 
            +
                              elsif motor.down_limit
         | 
| 114 | 
            +
                                motor.publish(:"ip#{message.ip}_percent", message.position_pulses.to_f / motor.down_limit * 100)
         | 
| 120 115 | 
             
                              end
         | 
| 116 | 
            +
                            when Message::PostGroupAddr
         | 
| 117 | 
            +
                              motor.add_group(message.group_index, message.group_address)
         | 
| 118 | 
            +
                            end
         | 
| 121 119 |  | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 120 | 
            +
                            @mutex.synchronize do
         | 
| 121 | 
            +
                              prior_message_to_group = Message.group_address?(@prior_message&.message&.src) if @prior_message
         | 
| 124 122 |  | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 123 | 
            +
                              correct_response = @response_pending && @prior_message&.message&.class&.expected_response?(message)
         | 
| 124 | 
            +
                              correct_response = false if !prior_message_to_group && message.src != @prior_message&.message&.dest
         | 
| 125 | 
            +
                              correct_response = false if prior_message_to_group && message.dest != @prior_message&.message&.src
         | 
| 128 126 |  | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 127 | 
            +
                              if prior_message_to_group && correct_response
         | 
| 128 | 
            +
                                @pending_group_motors.delete(Message.print_address(message.src).delete("."))
         | 
| 129 | 
            +
                                correct_response = false unless @pending_group_motors.empty?
         | 
| 130 | 
            +
                              end
         | 
| 133 131 |  | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 132 | 
            +
                              signal = correct_response || !follow_ups.empty?
         | 
| 133 | 
            +
                              @response_pending = @broadcast_pending if correct_response
         | 
| 134 | 
            +
                              follow_ups.each do |follow_up|
         | 
| 135 | 
            +
                                unless @queue.any? { |mr| mr.message == follow_up }
         | 
| 136 | 
            +
                                  @queue.push(MessageAndRetries.new(follow_up, 5, 1))
         | 
| 138 137 | 
             
                                end
         | 
| 139 | 
            -
                                @cond.signal if signal
         | 
| 140 138 | 
             
                              end
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                              SDN.logger.fatal "EOF reading"
         | 
| 143 | 
            -
                              exit 2
         | 
| 144 | 
            -
                            rescue MalformedMessage => e
         | 
| 145 | 
            -
                              SDN.logger.warn "ignoring malformed message: #{e}" unless e.to_s =~ /issing data/
         | 
| 146 | 
            -
                            rescue => e
         | 
| 147 | 
            -
                              SDN.logger.error "got garbage: #{e}; #{e.backtrace}"
         | 
| 139 | 
            +
                              @cond.signal if signal
         | 
| 148 140 | 
             
                            end
         | 
| 141 | 
            +
                          rescue EOFError
         | 
| 142 | 
            +
                            SDN.logger.fatal "EOF reading"
         | 
| 143 | 
            +
                            exit 2
         | 
| 144 | 
            +
                          rescue MalformedMessage => e
         | 
| 145 | 
            +
                            SDN.logger.warn "Ignoring malformed message: #{e}" unless e.to_s.include?("issing data")
         | 
| 146 | 
            +
                          rescue => e
         | 
| 147 | 
            +
                            SDN.logger.error "Got garbage: #{e}; #{e.backtrace}"
         | 
| 149 148 | 
             
                          end
         | 
| 150 149 | 
             
                        end
         | 
| 151 150 | 
             
                      end
         |