분 추가 (스케쥴 new 추가 필요)

This commit is contained in:
RubyOn 2025-04-18 15:54:19 +09:00
parent cd7620672f
commit eb3bc33509
10 changed files with 127 additions and 48 deletions

View File

@ -4,6 +4,16 @@ class ModbusController < ApplicationController
@modbus_running = Modbus::PollingService.running? @modbus_running = Modbus::PollingService.running?
end 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 def start
Modbus::PollingService.start Modbus::PollingService.start
redirect_to modbus_index_path redirect_to modbus_index_path
@ -19,17 +29,23 @@ class ModbusController < ApplicationController
end end
def schedule_edit_update def schedule_edit_update
error_hours = [] error_time = []
params[:schedule].each do |id, attributes| params[:schedule].each do |id, attributes|
schedule = Schedule.find_by(id: id) schedule = Schedule.find_by(id: id)
unless schedule.update(temperature: attributes[:temperature]) unless schedule.update(
error_hours << "#{schedule.hour}" hour: attributes[:hour],
minute: attributes[:minute],
is_active: attributes[:is_active],
temperature: attributes[:temperature]
)
error_time << "#{schedule.hour}#{schedule.minute}"
end end
end end
if error_hours.any? if error_time.any?
redirect_to modbus_index_path, alert: "#{error_hours.join(', ')}의 온도 업데이트에 실패하였습니다." redirect_to modbus_index_path, alert: "#{error_time.join(', ')}의 온도 업데이트에 실패하였습니다."
else else
redirect_to modbus_index_path, notice: "스케줄이 성공적으로 업데이트되었습니다." redirect_to modbus_index_path, notice: "스케줄이 성공적으로 업데이트되었습니다."
end end

View File

@ -1,6 +1,9 @@
class Schedule < ApplicationRecord class Schedule < ApplicationRecord
validates :hour, presence: true
validates :minute, presence: true
validates :temperature, validates :temperature,
presence: true, presence: true,
numericality: true, numericality: true,
format: { with: /\A\d+(\.\d)?\z/ } format: { with: /\A\d+(\.\d)?\z/ }
validates :minute, uniqueness: { scope: :hour }
end end

View File

@ -3,45 +3,68 @@ module Modbus
class << self class << self
def start def start
return if $modbus_polling_threads.any?(&:alive?) return if $modbus_polling_threads.any?(&:alive?)
$modbus_polling_threads << start_thread
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
end end
def stop def stop
$modbus_polling_threads.each { |t| t.kill if t.alive? } $modbus_polling_threads.each { |t| t.kill if t.alive? }
$modbus_polling_threads.clear $modbus_polling_threads.clear
puts "[#{Time.now}] Modbus polling service 중지됨" puts "Modbus polling service 중지됨"
end end
def running? def running?
$modbus_polling_threads.any?(&:alive?) $modbus_polling_threads.any?(&:alive?)
end 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 end
end end

View File

@ -23,12 +23,21 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
</head> </head>
<body class="h-screen"> <body>
<%= turbo_frame_tag :modals %> <%= turbo_frame_tag :modals %>
<main class="flex flex-col h-full divide-y divide-border-table-border"> <main class="flex flex-col h-full divide-y divide-border-table-border">
<%= render "partials/header" %> <%= render "partials/header" %>
<div class="flex flex-row flex-1 w-full divide-x divide-border-table-border"> <div class="flex flex-row flex-1 w-full divide-x divide-border-table-border">
<div class="w-full h-full"> <div class="w-full h-full">
<% if flash[:notice] %>
<div class="m-4 rounded px-4 py-2 bg-accept text-white">
<%= flash[:notice] %>
</div>
<% elsif flash[:alert] %>
<div class="m-4 rounded px-4 py-2 bg-danger text-white">
<%= flash[:alert] %>
</div>
<% end %>
<%= yield %> <%= yield %>
</div> </div>
</div> </div>

View File

@ -1,18 +1,11 @@
<% if flash[:notice] %>
<div class="m-4 rounded px-4 py-2 bg-accept text-white">
<%= flash[:notice] %>
</div>
<% elsif flash[:alert] %>
<div class="m-4 rounded px-4 py-2 bg-danger text-white">
<%= flash[:alert] %>
</div>
<% end %>
<div class="flex flex-col h-full divide-y divide-border-table-border"> <div class="flex flex-col h-full divide-y divide-border-table-border">
<div class="flex flex-col flex-1 p-4 space-y-4"> <div class="flex flex-col flex-1 p-4 space-y-4">
<table class="base-table"> <table class="base-table">
<thead> <thead>
<tr> <tr>
<th>시간</th> <th>시간</th>
<th>분</th>
<th>사용여부</th>
<th>온도</th> <th>온도</th>
</tr> </tr>
</thead> </thead>
@ -20,6 +13,8 @@
<% @schedule.each do |s| %> <% @schedule.each do |s| %>
<tr> <tr>
<td><%= s.hour %>시</td> <td><%= s.hour %>시</td>
<td><%= s.minute %>분</td>
<td><%= s.is_active %></td>
<td><%= s.temperature %> °C</td> <td><%= s.temperature %> °C</td>
</tr> </tr>
<% end %> <% end %>

View File

@ -4,15 +4,30 @@
<thead> <thead>
<tr> <tr>
<th>시간</th> <th>시간</th>
<th>분</th>
<th>사용여부</th>
<th>온도</th> <th>온도</th>
<th>삭제</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% @schedule.each do |s| %> <% @schedule.each do |s| %>
<tr> <tr>
<td><%= s.hour %>시</td> <td>
<td><%= number_field_tag "schedule[#{s.id}][temperature]", s.temperature, step: "0.1", inputmode: "decimal", class: "border px-2 py-1 rounded" %> <%= select_tag "schedule[#{s.id}][hour]",
°C options_for_select((0..23).map { |h| [h.to_s.rjust(2, '0'), h] }, s.hour),
class: "input-style" %>
</td>
<td><%= number_field_tag "schedule[#{s.id}][minute]", s.minute, min: 0, max: 59, step: 1, inputmode: "decimal", class: "input-style" %></td>
<td><%= check_box_tag "schedule[#{s.id}][is_active]", "1", s.is_active == true || s.is_active == 1 %></td>
<td><%= number_field_tag "schedule[#{s.id}][temperature]", s.temperature, step: "0.1", inputmode: "decimal", class: "input-style" %></td>
<td>
<%= link_to "삭제", modbus_path(s),
data: {
turbo_method: :delete,
turbo_confirm: "정말 삭제하시겠습니까?"
},
class: "btn bg-danger text-sm" %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

View File

@ -2,9 +2,12 @@ class CreateSchedules < ActiveRecord::Migration[8.0]
def change def change
create_table :schedules do |t| create_table :schedules do |t|
t.integer :hour t.integer :hour
t.integer :minute
t.boolean :is_active
t.float :temperature t.float :temperature
t.timestamps t.timestamps
end end
add_index :schedules, [ :hour, :minute ], unique: true
end end
end end

3
db/schema.rb generated
View File

@ -13,8 +13,11 @@
ActiveRecord::Schema[8.0].define(version: 2025_04_16_131440) do ActiveRecord::Schema[8.0].define(version: 2025_04_16_131440) do
create_table "schedules", force: :cascade do |t| create_table "schedules", force: :cascade do |t|
t.integer "hour" t.integer "hour"
t.integer "minute"
t.boolean "is_active"
t.float "temperature" t.float "temperature"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["hour", "minute"], name: "index_schedules_on_hour_and_minute", unique: true
end end
end end

View File

@ -9,5 +9,5 @@
# end # end
(0..23).each do | h | (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 end

12
on_off.rb Normal file
View File

@ -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