<commit_before>require 'serialport'
require 'redis'
require 'thread'
require 'json'
require 'yaml'

module AlarmDecoder
  PORT      = "/dev/ttyUSB0"
  BAUD      = 115200
  DATA_BITS = 8

  def self.listen(redis = Redis.new)
    interrupted = false
    trap("INT") do
      interrupted = true
      puts "Quitting"
    end

    SerialPort.open(PORT, "baud" => BAUD, "data_bits" => DATA_BITS) do |sp|
      write_thread = Thread.new do
        begin
          write_redis = Redis.new
          write_redis.subscribe 'alarm_decoder_write' do |on|
            on.message do |channel, message|
              sp.puts message
            end
          end
        rescue Redis::BaseConnectionError => error
          puts "Write Thread: #{error}, retrying in 1s"
          sleep 1
          retry
        end
      end

      while (i = sp.gets.chomp) && !interrupted do
        puts i
        if message = parse_message(i)
          redis.publish 'alarm_decoder', message.to_json
        end
      end
      write_thread.kill
    end
  end

  def self.watch(redis = Redis.new)
    redis.subscribe 'alarm_decoder' do |on|
      on.message do |channel, message|
        yield JSON.parse(message)
      end
    end
  rescue Redis::BaseConnectionError => error
    puts "#{error}, retrying in 1s"
    sleep 1
    retry
  end

  def self.write(message, redis = Redis.new)
    redis.publish 'alarm_decoder_write', message
  end

  private

  def self.parse_message(raw_message)
    return unless raw_message[/\[/]
    split_message = raw_message.split(',')
    bit_field = split_message[0].gsub(/\[|\]/, '').chars.map(&:to_i)
    zone = split_message[1].to_i
    zone_name = config.fetch("zones", {})[zone]

    {
      ready:          bit_field[0]  == 1,
      armed_away:     bit_field[1]  == 1,
      armed_home:     bit_field[2]  == 1,
      alarm_occured:  bit_field[10] == 1,
      alarm_sounding: bit_field[11] == 1,
      armed_instant:  bit_field[13] == 1,
      fire:           bit_field[14] == 1,
      zone_issue:     bit_field[15] == 1,
      perimeter_only: bit_field[16] == 1,
      zone_number:    zone,
      zone_name:      zone_name
    }
  end

  def self.config
    @config ||= YAML.load_file(File.join(__dir__, '../.alarm_decoder.yml')) || {}
  end
end<commit_msg>Fix off by one errors when parsing status<commit_after>require 'serialport'
require 'redis'
require 'thread'
require 'json'
require 'yaml'

module AlarmDecoder
  PORT      = "/dev/ttyUSB0"
  BAUD      = 115200
  DATA_BITS = 8

  def self.listen(redis = Redis.new)
    interrupted = false
    trap("INT") do
      interrupted = true
      puts "Quitting"
    end

    SerialPort.open(PORT, "baud" => BAUD, "data_bits" => DATA_BITS) do |sp|
      write_thread = Thread.new do
        begin
          write_redis = Redis.new
          write_redis.subscribe 'alarm_decoder_write' do |on|
            on.message do |channel, message|
              sp.puts message
            end
          end
        rescue Redis::BaseConnectionError => error
          puts "Write Thread: #{error}, retrying in 1s"
          sleep 1
          retry
        end
      end

      while (i = sp.gets.chomp) && !interrupted do
        puts i
        if message = parse_message(i)
          redis.publish 'alarm_decoder', message.to_json
        end
      end
      write_thread.kill
    end
  end

  def self.watch(redis = Redis.new)
    redis.subscribe 'alarm_decoder' do |on|
      on.message do |channel, message|
        yield JSON.parse(message)
      end
    end
  rescue Redis::BaseConnectionError => error
    puts "#{error}, retrying in 1s"
    sleep 1
    retry
  end

  def self.write(message, redis = Redis.new)
    redis.publish 'alarm_decoder_write', message
  end

  private

  def self.parse_message(raw_message)
    return unless raw_message[/\[/]
    split_message = raw_message.split(',')
    bit_field = split_message[0].gsub(/\[|\]/, '').chars.map(&:to_i)
    zone = split_message[1].to_i
    zone_name = config.fetch("zones", {})[zone]

    {
      ready:          bit_field[0]  == 1,
      armed_away:     bit_field[1]  == 1,
      armed_home:     bit_field[2]  == 1,
      alarm_occured:  bit_field[9] == 1,
      alarm_sounding: bit_field[10] == 1,
      armed_instant:  bit_field[12] == 1,
      fire:           bit_field[13] == 1,
      zone_issue:     bit_field[14] == 1,
      perimeter_only: bit_field[15] == 1,
      zone_number:    zone,
      zone_name:      zone_name
    }
  end

  def self.config
    @config ||= YAML.load_file(File.join(__dir__, '../.alarm_decoder.yml')) || {}
  end
end
{'fraction_non_alphanumeric': 0.11453551912568306, 'fraction_numerical': 0.01639344262295082, 'mean_word_length': 2.2, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7207886', 'n_tokens_mistral': 1610, 'n_tokens_neox': 1549, 'n_words': 362}