From eb3bc3350983563f9909dd22d7637599ba129ba5 Mon Sep 17 00:00:00 2001 From: RubyOn Date: Fri, 18 Apr 2025 15:54:19 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B6=84=20=EC=B6=94=EA=B0=80=20(=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A5=B4=20new=20=EC=B6=94=EA=B0=80=20=ED=95=84?= =?UTF-8?q?=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/modbus_controller.rb | 26 ++++-- app/models/schedule.rb | 3 + app/services/modbus/polling_service.rb | 81 ++++++++++++------- app/views/layouts/application.html.erb | 11 ++- app/views/modbus/index.html.erb | 13 +-- app/views/modbus/schedule_edit.html.erb | 21 ++++- db/migrate/20250416131440_create_schedules.rb | 3 + db/schema.rb | 3 + db/seeds.rb | 2 +- on_off.rb | 12 +++ 10 files changed, 127 insertions(+), 48 deletions(-) create mode 100644 on_off.rb diff --git a/app/controllers/modbus_controller.rb b/app/controllers/modbus_controller.rb index 380dcda..cdaf8d3 100644 --- a/app/controllers/modbus_controller.rb +++ b/app/controllers/modbus_controller.rb @@ -4,6 +4,16 @@ class ModbusController < ApplicationController @modbus_running = Modbus::PollingService.running? end + def destroy + schedule = Schedule.find_by(id: params[:id]) + + if schedule.destroy + redirect_to schedule_edit_modbus_index_path, notice: "스케줄이 삭제되었습니다." + else + redirect_to schedule_edit_modbus_index_path, alert: "스케줄 삭제에 실패했습니다." + end + end + def start Modbus::PollingService.start redirect_to modbus_index_path @@ -19,17 +29,23 @@ class ModbusController < ApplicationController end def schedule_edit_update - error_hours = [] + error_time = [] params[:schedule].each do |id, attributes| schedule = Schedule.find_by(id: id) - unless schedule.update(temperature: attributes[:temperature]) - error_hours << "#{schedule.hour}시" + unless schedule.update( + hour: attributes[:hour], + minute: attributes[:minute], + is_active: attributes[:is_active], + temperature: attributes[:temperature] + ) + + error_time << "#{schedule.hour}시 #{schedule.minute}분" end end - if error_hours.any? - redirect_to modbus_index_path, alert: "#{error_hours.join(', ')}의 온도 업데이트에 실패하였습니다." + if error_time.any? + redirect_to modbus_index_path, alert: "#{error_time.join(', ')}의 온도 업데이트에 실패하였습니다." else redirect_to modbus_index_path, notice: "스케줄이 성공적으로 업데이트되었습니다." end diff --git a/app/models/schedule.rb b/app/models/schedule.rb index 741d2ff..1bc1090 100644 --- a/app/models/schedule.rb +++ b/app/models/schedule.rb @@ -1,6 +1,9 @@ class Schedule < ApplicationRecord + validates :hour, presence: true + validates :minute, presence: true validates :temperature, presence: true, numericality: true, format: { with: /\A\d+(\.\d)?\z/ } + validates :minute, uniqueness: { scope: :hour } end diff --git a/app/services/modbus/polling_service.rb b/app/services/modbus/polling_service.rb index 7da77b2..9b5d5c0 100644 --- a/app/services/modbus/polling_service.rb +++ b/app/services/modbus/polling_service.rb @@ -3,45 +3,68 @@ module Modbus class << self def start return if $modbus_polling_threads.any?(&:alive?) - - thread = Thread.new do - puts "[#{Time.current}] Modbus polling service 시작됨" - last_logged_hour = nil - loop do - begin - now = Time.now - current_hour = now.hour - - if current_hour != last_logged_hour && now.min == 0 - schedule = Schedule.find_by(hour: current_hour) - - serial_path = Rails.root.join("serial.rb") - system("ruby", serial_path.to_s, "#{schedule.temperature * 10}") - - puts "[Schedule] #{current_hour}:00 -> Target temp: #{schedule.temperature}°C" - last_logged_hour = current_hour - end - rescue StandardError => e - error_message = "[#{Time.current}] 오류: #{e.message}" - - puts error_message - ensure - sleep 0.1 - end - end - end - $modbus_polling_threads << thread + $modbus_polling_threads << start_thread end def stop $modbus_polling_threads.each { |t| t.kill if t.alive? } $modbus_polling_threads.clear - puts "[#{Time.now}] Modbus polling service 중지됨" + puts "Modbus polling service 중지됨" end def running? $modbus_polling_threads.any?(&:alive?) end + + private + def start_thread + Thread.new { run_polling_loop } + end + + def run_polling_loop + puts "Modbus polling service 시작됨" + last_logged = nil + + loop do + begin + now = Time.now + current = [ now.hour, now.min ] + + if current != last_logged + puts "시간 변경 감지됨: #{current.join(':')}" + schedule = Schedule.find_by(hour: current[0], minute: current[1]) + apply_schedule(schedule) if schedule + last_logged = current + end + rescue => e + puts "[#{Time.current}] #{file} 에러: #{e.message}" + ensure + sleep 1 + end + end + end + + def apply_schedule(schedule) + if schedule.is_active + run_script("on_off.rb", "0", "[Schedule] ON") + run_script( + "serial.rb", + (schedule.temperature * 10).to_i.to_s, + "[Schedule] #{schedule.hour}:#{schedule.minute} → #{schedule.temperature}°C" + ) + else + run_script("on_off.rb", "1", "[Schedule] OFF") + end + end + + def run_script(file, arg, success_msg) + path = Rails.root.join(file) + if system("ruby", path.to_s, arg) + puts success_msg + else + puts "[#{Time.current}] #{file} 실행 실패 (args: #{arg})" + end + end end end end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0e9d66b..14ced63 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -23,12 +23,21 @@ - + <%= turbo_frame_tag :modals %>
<%= render "partials/header" %>
+ <% if flash[:notice] %> +
+ <%= flash[:notice] %> +
+ <% elsif flash[:alert] %> +
+ <%= flash[:alert] %> +
+ <% end %> <%= yield %>
diff --git a/app/views/modbus/index.html.erb b/app/views/modbus/index.html.erb index 6fc8d6b..9268ef3 100644 --- a/app/views/modbus/index.html.erb +++ b/app/views/modbus/index.html.erb @@ -1,18 +1,11 @@ -<% if flash[:notice] %> -
- <%= flash[:notice] %> -
-<% elsif flash[:alert] %> -
- <%= flash[:alert] %> -
-<% end %>
+ + @@ -20,6 +13,8 @@ <% @schedule.each do |s| %> + + <% end %> diff --git a/app/views/modbus/schedule_edit.html.erb b/app/views/modbus/schedule_edit.html.erb index 2bed1d4..ef26832 100644 --- a/app/views/modbus/schedule_edit.html.erb +++ b/app/views/modbus/schedule_edit.html.erb @@ -4,15 +4,30 @@ + + + <% @schedule.each do |s| %> - - + + + + <% end %> diff --git a/db/migrate/20250416131440_create_schedules.rb b/db/migrate/20250416131440_create_schedules.rb index 1ed0d45..51df18e 100644 --- a/db/migrate/20250416131440_create_schedules.rb +++ b/db/migrate/20250416131440_create_schedules.rb @@ -2,9 +2,12 @@ class CreateSchedules < ActiveRecord::Migration[8.0] def change create_table :schedules do |t| t.integer :hour + t.integer :minute + t.boolean :is_active t.float :temperature t.timestamps end + add_index :schedules, [ :hour, :minute ], unique: true end end diff --git a/db/schema.rb b/db/schema.rb index afe3d8e..cab8406 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,8 +13,11 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_16_131440) do create_table "schedules", force: :cascade do |t| t.integer "hour" + t.integer "minute" + t.boolean "is_active" t.float "temperature" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["hour", "minute"], name: "index_schedules_on_hour_and_minute", unique: true end end diff --git a/db/seeds.rb b/db/seeds.rb index b19f220..953e2e0 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -9,5 +9,5 @@ # end (0..23).each do | h | - Schedule.create!(hour: h, temperature: 15.0) + Schedule.create!(hour: h, minute: 0, is_active: true, temperature: 15.0) end diff --git a/on_off.rb b/on_off.rb new file mode 100644 index 0000000..032e304 --- /dev/null +++ b/on_off.rb @@ -0,0 +1,12 @@ +require "rmodbus" +require "ccutrer-serialport" + +value = ARGV[0]&.to_i + +ModBus::RTUClient.new("/dev/ttyUSB0", 9600) do |cl| + cl.with_slave(7) do |slave| + regs = slave.holding_registers + regs[16] = value + sleep 0.1 + end +end
시간사용여부 온도
<%= s.hour %>시<%= s.minute %>분<%= s.is_active %> <%= s.temperature %> °C
시간사용여부 온도삭제
<%= s.hour %>시<%= number_field_tag "schedule[#{s.id}][temperature]", s.temperature, step: "0.1", inputmode: "decimal", class: "border px-2 py-1 rounded" %> - °C + + <%= select_tag "schedule[#{s.id}][hour]", + options_for_select((0..23).map { |h| [h.to_s.rjust(2, '0'), h] }, s.hour), + class: "input-style" %> + <%= number_field_tag "schedule[#{s.id}][minute]", s.minute, min: 0, max: 59, step: 1, inputmode: "decimal", class: "input-style" %><%= check_box_tag "schedule[#{s.id}][is_active]", "1", s.is_active == true || s.is_active == 1 %><%= number_field_tag "schedule[#{s.id}][temperature]", s.temperature, step: "0.1", inputmode: "decimal", class: "input-style" %> + <%= link_to "삭제", modbus_path(s), + data: { + turbo_method: :delete, + turbo_confirm: "정말 삭제하시겠습니까?" + }, + class: "btn bg-danger text-sm" %>