OpenScan-org / OpenScan2

an improved firmware for the OpenScan devices, featuring many more cameras and a greatly improved overall usability
GNU General Public License v3.0
254 stars 29 forks source link

Feature Request: Pause Scan #24

Closed OpenScanFan closed 2 years ago

OpenScanFan commented 2 years ago

It would be really handy to add a pause scan button Pause_Button

sokol07 commented 2 years ago

@OpenScanEu I tried to implement pause function, however, due to the structure of the node-red code (mix of function blocks and python function blocks) I couldn't find a nice solution for this... I guess we would need a button to pass pause command to the "Routine" block (and then lock the "for" loop with "while" statement). Maybe that's due to my lack of knowledge of node-red but I couldn't manage to find a nice way to pass the pause command (damn python-3 blocks incompatible with internal node-red variables). If we need to pass the command to the python-3 block, we need to use a file (save/readstring - that's generally quite problematic in my opinion, maybe we somehow could minimize the use or write operations to limit the stress of the sd card?). But to execute a single write from the pause button we have to use a node-red function block?

sokol07 commented 2 years ago

Ok, it took me a while but I managed to implement this. In the SCAN flow a few new blocks are needed: image

They need to have code as below: (click) ``` [ { "id": "70e59042ca7c4455", "type": "ui_button", "z": "1613373abaf77a2c", "name": "pause", "group": "7aaf184330605300", "order": 27, "width": "4", "height": "1", "passthru": false, "label": "", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "fa-pause", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 330, "y": 1460, "wires": [ [ "2315704a2f3ba373" ] ] }, { "id": "2315704a2f3ba373", "type": "file in", "z": "1613373abaf77a2c", "name": "read status", "filename": "/home/pi/OpenScan/settings/status_internal_cam", "format": "utf8", "chunk": false, "sendError": false, "encoding": "none", "allProps": false, "x": 470, "y": 1460, "wires": [ [ "7be623536d108880" ] ] }, { "id": "7be623536d108880", "type": "switch", "z": "1613373abaf77a2c", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "eq", "v": "PAUSED", "vt": "str" }, { "t": "neq", "v": "PAUSED", "vt": "str" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 670, "y": 1460, "wires": [ [ "bb2c27b2ba52fa60" ], [ "9fbcc55645bf3673" ] ] }, { "id": "bb2c27b2ba52fa60", "type": "function", "z": "1613373abaf77a2c", "name": "", "func": "msg.payload = \"RESUMING\"\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 820, "y": 1420, "wires": [ [ "3da955ba25219537" ] ] }, { "id": "9fbcc55645bf3673", "type": "function", "z": "1613373abaf77a2c", "name": "", "func": "msg.payload = \"PAUSED\"\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 820, "y": 1500, "wires": [ [ "3da955ba25219537" ] ] }, { "id": "3da955ba25219537", "type": "file", "z": "1613373abaf77a2c", "name": "write_status", "filename": "/home/pi/OpenScan/settings/status_internal_cam", "appendNewline": false, "createDir": false, "overwriteFile": "true", "encoding": "none", "x": 990, "y": 1460, "wires": [ [] ] }, { "id": "7aaf184330605300", "type": "ui_group", "name": "Settings", "tab": "e23b837a9f040895", "order": 1, "disp": false, "width": "6", "collapse": false, "className": "" }, { "id": "e23b837a9f040895", "type": "ui_tab", "name": "Scan", "icon": "dashboard", "order": 2, "disabled": false, "hidden": false } ] ```

Moreover, the "Routine" code need to be changed to:

(click) ``` from OpenScan import load_str, load_int, motorrun, create_coordinates, take_photo, save, load_bool, camera from time import sleep, strftime from zipfile import ZipFile, ZIP_DEFLATED from os import system from os.path import isfile from Arducam import Focuser if load_str("status_internal_cam")=="no camera found" or load_str("status_internal_cam")[:5]=="Featu": return save('status_internal_cam','Routine-preparing') sleep(2) projectname=load_str("routine_projectname") photocount = load_int('routine_photocount') #vorher point_count angle_max = load_int('rotor_anglemax') angle_min = load_int('rotor_anglemin') angle_start = load_int('rotor_anglestart') projectcode = strftime('20%y-%m-%d_%H.%M.%S-') + projectname cam = load_str('camera') STmode = load_bool('cam_STmode') if cam == 'imx519' and STmode == True: focuser = Focuser('/dev/v4l-subdev1') stacksize = load_int('cam_stacksize') focus1 = load_int('cam_focus1') focus2 = load_int('cam_focus2') if focus1 > focus2: focus2 = focus1 focus1 = load_int('cam_focus2') focusstep = int((focus2-focus1)/(stacksize - 1)) coordinates = create_coordinates(angle_min,angle_max,photocount) position_last = (angle_start , 0) counter = 0 basepath = '/home/pi/OpenScan/' temppath = basepath + 'tmp/tmp.jpg' zippath = basepath + 'tmp/tmp.zip' if isfile(zippath): system('rm ' + zippath) zip = ZipFile(zippath, "w",ZIP_DEFLATED, allowZip64=True) for position in coordinates: while load_str('status_internal_cam') == "PAUSED": sleep(1) counter += 1 if load_str('status_internal_cam') == "Routine-stopping": break filepath = basepath + 'tmp/' + projectname + '_' + str(counter) + ".jpg" rotor_angle = position_last[0] - position[0] if abs(rotor_angle) > 180: rotor_angle = -360 * rotor_angle/abs(rotor_angle) + rotor_angle tt_angle = position_last[1] - position[1] if abs(tt_angle) > 180: tt_angle = -360 * tt_angle/abs(tt_angle) + tt_angle motorrun('rotor', rotor_angle) motorrun('tt', tt_angle) msg['cropx'] = load_int('cam_cropx') msg['cropy'] = load_int('cam_cropy') msg['rotation'] = load_int('cam_rotation') msg['filepath_in'] = 'tmp/tmp.jpg' msg['filepath_out'] = 'tmp/tmp.jpg' msg['filepath'] = 'tmp/tmp.jpg' if STmode == True: counter2 = 0 for focus in range (stacksize): if load_str('status_internal_cam') == "Routine-stopping": break counter2 += 1 save('status_internal_cam','Routine-' + str(counter) + '/' + str(photocount) + ' F' + str(counter2)) focuser.write(focus1 + focus * focusstep) take_photo('tmp/tmp.jpg') camera('/crop',msg) zip.write(temppath, projectname + '_' + str(counter) + '-' + str(focus) + ".jpg") system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg') elif cam != 'external': save('status_internal_cam','Routine-Photo ' + str(counter) + '/' + str(photocount)) if cam == 'gphoto': camera('/gphoto_capture', msg) if cam in ('imx219','ov5647','imx477','imx290a','imx290b','imx378','ov9281','imx519'): take_photo('tmp/tmp.jpg') camera('/crop',msg) zip.write(temppath, projectname + '_' + str(counter) + ".jpg") system('cp ' + temppath + ' ' + basepath +'tmp/preview.jpg') elif cam == 'external': camera('external_capture') position_last = position zip.close() system('mv '+ zippath + ' ' + basepath + 'scans/' + projectcode + '.zip') save('status_internal_cam','Routine-done') motorrun('rotor',position_last[0] - angle_start) motorrun('tt',position_last[1]) save('status_internal_cam','--READY--') msg['enabled'] = False return msg ```

Finally, the dashboard elements needs to be placed in such configuration: image

The pause button has got the pause icon and width of 4x1.

All this is implemented in my fork #20

The result is a pause button: image

It is active only when scanning is running, when device is paused the status will be changed to "PAUSED". The device can be resumed by clicking the pause button again or stopped by stop button.

OpenScanFan commented 2 years ago

Sorry, but I have a couple of questions

id": "70e59042ca7c4455", "type": "ui_button", "z": "1613373abaf77a2c", <--- z? "name": "pause", "group": "7aaf184330605300", <----what group is this? where do I enter it? "order": 27, <---where can I change the order? "width": "4", "height": "1", "passthru": false, "label": "", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "fa-pause", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 330, <----where do I enter these values? "y": 1460, "wires": <----where is this?

Sorry, I am still learning this

sokol07 commented 2 years ago

You can import the blocks using the json code (https://nodered.org/docs/user-guide/editor/workspace/import-export) The values you marked are not mean to be input by hand, I guess that group describes to which dashboard the button is added (my second screen, you can follow one of the tutorials about dashboards to find this window, for example: https://www.arubacloud.com/tutorial/how-to-create-a-node-red-dashboard.aspx), order describes position in the dashboard. X and Y are probably just positions of this block in the node red editor sheet and wires describe connections to other nodes in the flow (black lines connecting the blocks). I have no idea what is the z value but as I mentioned - I don't think you should bother, I believe that you can just import the blocks from the json provided, adjust the connectors and function code and place the button in right place in the dashboard layout list.

OpenScanEu commented 2 years ago

@sokol07 @OpenScanFan I have added a pause button to the latest update.