Skip to content

Commit

Permalink
Merge pull request #47 from int3ll3ct/master
Browse files Browse the repository at this point in the history
Proposed fix for issue #46
  • Loading branch information
CCoffie authored Mar 5, 2017
2 parents 1b7527e + eaa7f61 commit 6a16a56
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 40 deletions.
9 changes: 0 additions & 9 deletions .travis.yml

This file was deleted.

95 changes: 64 additions & 31 deletions openroast/controllers/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,104 @@

import json
import openroast

from multiprocessing import sharedctypes, Array
import ctypes

class Recipe(object):
def __init__(self):
self.currentRecipeStep = 0
def __init__(self, max_recipe_size_bytes=64*1024):
# this object is accessed by multiple processes, in part because
# freshroastsr700 calls Recipe.move_to_next_section() from a
# child process. Therefore, all data handling must be process-safe.

# recipe step currently being applied
self.currentRecipeStep = sharedctypes.Value('i', 0)
# Stores recipe
self.recipe = {}
# Here, we need to use shared memory to store the recipe.
# Tried multiprocessing.Manager, wasn't very successful with that,
# resorting to allocating a fixed-size, large buffer to store a JSON
# string. This Array needs to live for the lifetime of the object.
self.recipe_str = Array(ctypes.c_char, max_recipe_size_bytes)

# Tells if a recipe has been loaded
self.recipeLoaded = False
self.recipeLoaded = sharedctypes.Value('i', 0) # boolean

def _recipe(self):
# retrieve the recipe as a JSON string in shared memory.
# needed to allow freshroastsr700 to access Recipe from
# its child process
if self.recipeLoaded.value:
return json.loads(self.recipe_str.value.decode('utf_8'))
else:
return {}

def load_recipe_json(self, recipeJson):
self.recipe = recipeJson
self.recipeLoaded = True
# recipeJson is actually a dict...
self.recipe_str.value = json.dumps(recipeJson).encode('utf_8')
self.recipeLoaded.value = 1

def load_recipe_file(self, recipeFile):
# Load recipe file
recipeFileHandler = open(recipeFile)
self.recipe = json.load(recipeFileHandler)
recipe_dict = json.load(recipeFileHandler)
recipeFileHandler.close()
self.recipeLoaded = True
self.load_recipe_json(recipe_dict)

def clear_recipe(self):
self.recipeLoaded = False
self.recipe = {}
self.currentRecipeStep = 0
self.recipeLoaded.value = 0
self.recipe_str.value = ''.encode('utf_8')
self.currentRecipeStep.value = 0

def check_recipe_loaded(self):
return self.recipeLoaded
return self.recipeLoaded.value != 0

def get_num_recipe_sections(self):
return len(self.recipe["steps"])
if not self.check_recipe_loaded():
return 0
return len(self._recipe()["steps"])

def get_current_step_number(self):
return self.currentRecipeStep
return self.currentRecipeStep.value

def get_current_fan_speed(self):
return self.recipe["steps"][self.currentRecipeStep]["fanSpeed"]
crnt_step = self.currentRecipeStep.value
return self._recipe()["steps"][crnt_step]["fanSpeed"]

def get_current_target_temp(self):
if(self.recipe["steps"][self.currentRecipeStep].get("targetTemp")):
return self.recipe["steps"][self.currentRecipeStep]["targetTemp"]
crnt_step = self.currentRecipeStep.value
if(self._recipe()["steps"][crnt_step].get("targetTemp")):
return self._recipe()["steps"][crnt_step]["targetTemp"]
else:
return 150

def get_current_section_time(self):
return self.recipe["steps"][self.currentRecipeStep]["sectionTime"]
crnt_step = self.currentRecipeStep.value
return self._recipe()["steps"][crnt_step]["sectionTime"]

def restart_current_recipe(self):
self.currentRecipeStep = 0
self.currentRecipeStep.value = 0
self.load_current_section()

def more_recipe_sections(self):
if(len(self.recipe["steps"]) - self.currentRecipeStep == 0):
if not self.check_recipe_loaded():
return False
if(len(self._recipe()["steps"]) - self.currentRecipeStep.value == 0):
return False
else:
return True

def get_current_cooling_status(self):
if(self.recipe["steps"][self.currentRecipeStep].get("cooling")):
return self.recipe["steps"][self.currentRecipeStep]["cooling"]
crnt_step = self.currentRecipeStep.value
if(self._recipe()["steps"][crnt_step].get("cooling")):
return self._recipe()["steps"][crnt_step]["cooling"]
else:
return False

def get_section_time(self, index):
return self.recipe["steps"][index]["sectionTime"]
return self._recipe()["steps"][index]["sectionTime"]

def get_section_temp(self, index):
if(self.recipe["steps"][index].get("targetTemp")):
return self.recipe["steps"][index]["targetTemp"]
if(self._recipe()["steps"][index].get("targetTemp")):
return self._recipe()["steps"][index]["targetTemp"]
else:
return 150

Expand All @@ -87,7 +114,8 @@ def set_roaster_settings(self, targetTemp, fanSpeed, sectionTime, cooling):
openroast.roaster.cool()

# Prevent the roaster from starting when section time = 0 (ex clear)
if(not cooling and sectionTime > 0 and self.currentRecipeStep > 0):
if(not cooling and sectionTime > 0 and
self.currentRecipeStep.value > 0):
openroast.roaster.roast()

openroast.roaster.target_temp = targetTemp
Expand All @@ -101,15 +129,20 @@ def load_current_section(self):
self.get_current_cooling_status())

def move_to_next_section(self):
# this gets called from freshroastsr700's timer process, which
# is spawned using multiprocessing. Therefore, all things
# accessed in this function must be process-safe!
if self.check_recipe_loaded():
if (self.currentRecipeStep + 1) >= self.get_num_recipe_sections():
if(
(self.currentRecipeStep.value + 1) >=
self.get_num_recipe_sections()):
openroast.roaster.idle()
else:
self.currentRecipeStep += 1
self.currentRecipeStep.value += 1
self.load_current_section()
openroast.window.roast.update_controllers()
openroast.window.roast.schedule_update_controllers()
else:
openroast.roaster.idle()

def get_current_recipe(self):
return self.recipe
return self._recipe()
23 changes: 23 additions & 0 deletions openroast/views/roasttab.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import math
import datetime
import openroast
from multiprocessing import sharedctypes

from PyQt5 import QtCore
from PyQt5 import QtWidgets
Expand Down Expand Up @@ -34,6 +35,10 @@ def __init__(self):
self.timer.timeout.connect(self.update_data)
self.timer.start()

# Create a shared memory flag for scheduling the occasional call to
# update_controllers() from the the timer.
self._schedule_controller_update_flag = sharedctypes.Value('i', 0)

# Set the roast tab diabled when starting.
self.setEnabled(False)

Expand Down Expand Up @@ -104,6 +109,12 @@ def update_data(self):
self.connectionStatusLabel.setHidden(False)
self.setEnabled(False)

# if openroast.roaster has moved the recipe to the next section,
# update the controller-related info onscreen.
if(self._schedule_controller_update_flag.value):
self._schedule_controller_update_flag.value=0
self.update_controllers()

def create_right_pane(self):
rightPane = QtWidgets.QVBoxLayout()

Expand Down Expand Up @@ -444,5 +455,17 @@ def update_controllers(self):
self.update_target_temp()
self.update_fan_info()

def schedule_update_controllers(self):
"""This is designed to be called from other processes. Currently,
the openroast.roaster instance calls this function from a
child process. This object's timer routine (which periodically
calls update_data()) will pick up this flag at the next timer tick
and call update_controllers() at that time.
Alternately, we could have set up a complicated system to
support calling into the Pyqt app from a separate process - this
is easier, at the expense of being not quite immediate, graphically.
"""
self._schedule_controller_update_flag.value = 1

def get_recipe_object(self):
return openroast.recipes

0 comments on commit 6a16a56

Please sign in to comment.