33
44import json
55import openroast
6-
6+ from multiprocessing import sharedctypes , Array
7+ import ctypes
78
89class Recipe (object ):
9- def __init__ (self ):
10- self .currentRecipeStep = 0
10+ def __init__ (self , max_recipe_size_bytes = 64 * 1024 ):
11+ # this object is accessed by multiple processes, in part because
12+ # freshroastsr700 calls Recipe.move_to_next_section() from a
13+ # child process. Therefore, all data handling must be process-safe.
1114
15+ # recipe step currently being applied
16+ self .currentRecipeStep = sharedctypes .Value ('i' , 0 )
1217 # Stores recipe
13- self .recipe = {}
18+ # Here, we need to use shared memory to store the recipe.
19+ # Tried multiprocessing.Manager, wasn't very successful with that,
20+ # resorting to allocating a fixed-size, large buffer to store a JSON
21+ # string. This Array needs to live for the lifetime of the object.
22+ self .recipe_str = Array (ctypes .c_char , max_recipe_size_bytes )
1423
1524 # Tells if a recipe has been loaded
16- self .recipeLoaded = False
25+ self .recipeLoaded = sharedctypes .Value ('i' , 0 ) # boolean
26+
27+ def _recipe (self ):
28+ # retrieve the recipe as a JSON string in shared memory.
29+ # needed to allow freshroastsr700 to access Recipe from
30+ # its child process
31+ if self .recipeLoaded .value :
32+ return json .loads (self .recipe_str .value .decode ('utf_8' ))
33+ else :
34+ return {}
1735
1836 def load_recipe_json (self , recipeJson ):
19- self .recipe = recipeJson
20- self .recipeLoaded = True
37+ # recipeJson is actually a dict...
38+ self .recipe_str .value = json .dumps (recipeJson ).encode ('utf_8' )
39+ self .recipeLoaded .value = 1
2140
2241 def load_recipe_file (self , recipeFile ):
2342 # Load recipe file
2443 recipeFileHandler = open (recipeFile )
25- self . recipe = json .load (recipeFileHandler )
44+ recipe_dict = json .load (recipeFileHandler )
2645 recipeFileHandler .close ()
27- self .recipeLoaded = True
46+ self .load_recipe_json ( recipe_dict )
2847
2948 def clear_recipe (self ):
30- self .recipeLoaded = False
31- self .recipe = {}
32- self .currentRecipeStep = 0
49+ self .recipeLoaded . value = 0
50+ self .recipe_str . value = '' . encode ( 'utf_8' )
51+ self .currentRecipeStep . value = 0
3352
3453 def check_recipe_loaded (self ):
35- return self .recipeLoaded
54+ return self .recipeLoaded . value != 0
3655
3756 def get_num_recipe_sections (self ):
38- return len (self .recipe ["steps" ])
57+ if not self .check_recipe_loaded ():
58+ return 0
59+ return len (self ._recipe ()["steps" ])
3960
4061 def get_current_step_number (self ):
41- return self .currentRecipeStep
62+ return self .currentRecipeStep . value
4263
4364 def get_current_fan_speed (self ):
44- return self .recipe ["steps" ][self .currentRecipeStep ]["fanSpeed" ]
65+ crnt_step = self .currentRecipeStep .value
66+ return self ._recipe ()["steps" ][crnt_step ]["fanSpeed" ]
4567
4668 def get_current_target_temp (self ):
47- if (self .recipe ["steps" ][self .currentRecipeStep ].get ("targetTemp" )):
48- return self .recipe ["steps" ][self .currentRecipeStep ]["targetTemp" ]
69+ crnt_step = self .currentRecipeStep .value
70+ if (self ._recipe ()["steps" ][crnt_step ].get ("targetTemp" )):
71+ return self ._recipe ()["steps" ][crnt_step ]["targetTemp" ]
4972 else :
5073 return 150
5174
5275 def get_current_section_time (self ):
53- return self .recipe ["steps" ][self .currentRecipeStep ]["sectionTime" ]
76+ crnt_step = self .currentRecipeStep .value
77+ return self ._recipe ()["steps" ][crnt_step ]["sectionTime" ]
5478
5579 def restart_current_recipe (self ):
56- self .currentRecipeStep = 0
80+ self .currentRecipeStep . value = 0
5781 self .load_current_section ()
5882
5983 def more_recipe_sections (self ):
60- if (len (self .recipe ["steps" ]) - self .currentRecipeStep == 0 ):
84+ if not self .check_recipe_loaded ():
85+ return False
86+ if (len (self ._recipe ()["steps" ]) - self .currentRecipeStep .value == 0 ):
6187 return False
6288 else :
6389 return True
6490
6591 def get_current_cooling_status (self ):
66- if (self .recipe ["steps" ][self .currentRecipeStep ].get ("cooling" )):
67- return self .recipe ["steps" ][self .currentRecipeStep ]["cooling" ]
92+ crnt_step = self .currentRecipeStep .value
93+ if (self ._recipe ()["steps" ][crnt_step ].get ("cooling" )):
94+ return self ._recipe ()["steps" ][crnt_step ]["cooling" ]
6895 else :
6996 return False
7097
7198 def get_section_time (self , index ):
72- return self .recipe ["steps" ][index ]["sectionTime" ]
99+ return self ._recipe () ["steps" ][index ]["sectionTime" ]
73100
74101 def get_section_temp (self , index ):
75- if (self .recipe ["steps" ][index ].get ("targetTemp" )):
76- return self .recipe ["steps" ][index ]["targetTemp" ]
102+ if (self ._recipe () ["steps" ][index ].get ("targetTemp" )):
103+ return self ._recipe () ["steps" ][index ]["targetTemp" ]
77104 else :
78105 return 150
79106
@@ -87,7 +114,8 @@ def set_roaster_settings(self, targetTemp, fanSpeed, sectionTime, cooling):
87114 openroast .roaster .cool ()
88115
89116 # Prevent the roaster from starting when section time = 0 (ex clear)
90- if (not cooling and sectionTime > 0 and self .currentRecipeStep > 0 ):
117+ if (not cooling and sectionTime > 0 and
118+ self .currentRecipeStep .value > 0 ):
91119 openroast .roaster .roast ()
92120
93121 openroast .roaster .target_temp = targetTemp
@@ -101,15 +129,20 @@ def load_current_section(self):
101129 self .get_current_cooling_status ())
102130
103131 def move_to_next_section (self ):
132+ # this gets called from freshroastsr700's timer process, which
133+ # is spawned using multiprocessing. Therefore, all things
134+ # accessed in this function must be process-safe!
104135 if self .check_recipe_loaded ():
105- if (self .currentRecipeStep + 1 ) >= self .get_num_recipe_sections ():
136+ if (
137+ (self .currentRecipeStep .value + 1 ) >=
138+ self .get_num_recipe_sections ()):
106139 openroast .roaster .idle ()
107140 else :
108- self .currentRecipeStep += 1
141+ self .currentRecipeStep . value += 1
109142 self .load_current_section ()
110- openroast .window .roast .update_controllers ()
143+ openroast .window .roast .schedule_update_controllers ()
111144 else :
112145 openroast .roaster .idle ()
113146
114147 def get_current_recipe (self ):
115- return self .recipe
148+ return self ._recipe ()
0 commit comments