diff options
author | Suchita Lad | 2025-04-08 12:57:02 +0530 |
---|---|---|
committer | Suchita Lad | 2025-04-08 12:57:02 +0530 |
commit | 530c70390f2b399321a1b796de409351ad3ff1e2 (patch) | |
tree | 3449cb7defaf86f00e182599544ca9220e57fd38 | |
parent | 4b50b816c8cd6bbd39927b57b99a8499897b57fb (diff) | |
parent | 8ea1cdadc8f9488e973785bff6381183e02be846 (diff) | |
download | Common-Interface-Project-530c70390f2b399321a1b796de409351ad3ff1e2.tar.gz Common-Interface-Project-530c70390f2b399321a1b796de409351ad3ff1e2.tar.bz2 Common-Interface-Project-530c70390f2b399321a1b796de409351ad3ff1e2.zip |
Merged scripts with xcosblocks
-rw-r--r-- | blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js | 39 | ||||
-rw-r--r-- | blocks/eda-frontend/src/components/SchematicEditor/SimulationProperties.js | 17 | ||||
-rw-r--r-- | blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js | 209 | ||||
-rw-r--r-- | blocks/eda-frontend/src/redux/saveSchematicSlice.js | 10 | ||||
-rw-r--r-- | blocks/simulationAPI/helpers/config.py | 7 | ||||
-rw-r--r-- | blocks/simulationAPI/helpers/ngspice_helper.py | 105 | ||||
-rw-r--r-- | blocks/simulationAPI/helpers/scilab_manager.py | 462 | ||||
-rw-r--r-- | blocks/simulationAPI/tasks.py | 31 | ||||
-rw-r--r-- | blocks/simulationAPI/urls.py | 1 | ||||
-rw-r--r-- | blocks/simulationAPI/views.py | 36 |
10 files changed, 727 insertions, 190 deletions
diff --git a/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js b/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js index 8dcbf739..259803b4 100644 --- a/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js +++ b/blocks/eda-frontend/src/components/SchematicEditor/SchematicToolbar.js @@ -103,6 +103,7 @@ export default function SchematicToolbar ({ mobileClose, gridRef }) { const title2 = useSelector(state => state.saveSchematic.title) const scriptDump = useSelector(state => state.saveSchematic.scriptDump) + const showDot = useSelector(state => state.saveSchematic.showDot) const dispatch = useDispatch() const isMobile = useMediaQuery('(max-width:600px)') @@ -433,7 +434,43 @@ export default function SchematicToolbar ({ mobileClose, gridRef }) { { icon: <ImageOutlinedIcon fontSize='small' />, label: 'Image Export', action: handleImgClickOpen }, { icon: <PrintOutlinedIcon fontSize='small' />, label: 'Print Preview', action: PrintPreview }, 'pipe', - { icon: <DescriptionIcon fontSize='small' style={{ color: scriptDump ? 'red' : 'inherit' }} />, label: 'Show Script', action: handleSchWinOpen }, + { + icon: ( + <div style={{ position: 'relative', display: 'inline-block', cursor: 'pointer' }} onClick={handleSchWinOpen}> + <DescriptionIcon fontSize="small" style={{ color: scriptDump ? 'red' : 'inherit' }} /> + + {/* Blinking Dot */} + {showDot && ( + <div + style={{ + position: 'absolute', + top: '-3px', // Adjust position to be visible + right: '-3px', + width: '5px', + height: '5px', + borderRadius: '50%', + backgroundColor: 'green', + animation: 'blink-animation 1s infinite alternate', + zIndex: 10 // Ensure visibility + }} + /> + )} + + {/* CSS for blinking effect */} + <style> + {` + @keyframes blink-animation { + 0% { opacity: 1; } + 50% { opacity: 0.3; } + 100% { opacity: 1; } + } + `} + </style> + </div> + ), + label: 'Show Script', + action: handleSchWinOpen + }, { icon: <PlayCircleOutlineIcon fontSize='small' />, label: 'Simulate', action: handleNetlistOpen }, 'pipe', { icon: <UndoIcon fontSize='small' />, label: 'Undo', action: editorUndo }, diff --git a/blocks/eda-frontend/src/components/SchematicEditor/SimulationProperties.js b/blocks/eda-frontend/src/components/SchematicEditor/SimulationProperties.js index cd3a1704..fbfecbac 100644 --- a/blocks/eda-frontend/src/components/SchematicEditor/SimulationProperties.js +++ b/blocks/eda-frontend/src/components/SchematicEditor/SimulationProperties.js @@ -44,6 +44,7 @@ const useStyles = makeStyles((theme) => ({ export default function SimulationProperties () { const title = useSelector(state => state.saveSchematic.title) const isSimRes = useSelector(state => state.simulation.isSimRes) + const scriptTaskId = useSelector(state => state.simulation.scriptTaskId) const dispatch = useDispatch() const classes = useStyles() const [transientAnalysisControlLine, setTransientAnalysisControlLine] = useState({ @@ -81,16 +82,17 @@ export default function SimulationProperties () { // Prepare Netlist to file const prepareNetlist = (netlist) => { - const titleA = title.split(' ')[1] + const titleA = title const myblob = new Blob([netlist], { type: 'text/plain' }) const file = new File([myblob], `${titleA}.xml`, { type: 'text/xml', lastModified: Date.now() }) - sendNetlist(file) + const type = 'XCOS' + sendNetlist(file, type) } - function sendNetlist (file) { - netlistConfig(file) + function sendNetlist (file, type) { + netlistConfig(file, type) .then((response) => { const res = response.data const taskId = res.details.task_id @@ -102,10 +104,15 @@ export default function SimulationProperties () { } // Upload the nelist - async function netlistConfig (file) { + async function netlistConfig (file, type) { const formData = new FormData() formData.append('app_name', process.env.REACT_APP_NAME) formData.append('file', file) + formData.append('type', type) + if (scriptTaskId) { + formData.append('script_task_id', scriptTaskId) + } + for (const [key, value] of Object.entries(transientAnalysisControlLine)) { formData.append(key, value) } diff --git a/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js b/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js index 371f19d3..83c8ce15 100644 --- a/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js +++ b/blocks/eda-frontend/src/components/SchematicEditor/ToolbarExtension.js @@ -36,7 +36,7 @@ import { import { makeStyles } from '@material-ui/core/styles' import CloseIcon from '@material-ui/icons/Close' import { useSelector, useDispatch } from 'react-redux' -import { fetchSchematic, fetchDiagram, setSchScriptDump } from '../../redux/saveSchematicSlice' +import { fetchSchematic, fetchDiagram, setSchScriptDump, setShowDot } from '../../redux/saveSchematicSlice' import { fetchSchematics, fetchGallery } from '../../redux/dashboardSlice' import { setScriptTaskId } from '../../redux/simulationSlice' import { blue } from '@material-ui/core/colors' @@ -341,15 +341,18 @@ HelpScreen.propTypes = { export function ScriptScreen ({ isOpen, onClose }) { const scriptDump = useSelector(state => state.saveSchematic.scriptDump) const title = useSelector(state => state.saveSchematic.title) + const showDot = useSelector(state => state.saveSchematic.showDot) const dispatch = useDispatch() + const [result, setResult] = useState('No output yet...') + const [variables, setVariables] = useState([]) const scriptHandler = (e) => { dispatch(setSchScriptDump(e.target.value)) + dispatch(setShowDot(true)) } - const [result, setResult] = useState('') const prepareScriptNetlist = (scriptDump) => { - const titleA = title.split(' ')[1] + const titleA = title const myblob = new Blob([scriptDump], { type: 'text/plain' }) @@ -361,9 +364,14 @@ export function ScriptScreen ({ isOpen, onClose }) { function sendSriptNetlist (file, type) { netlistConfig(file, type) .then((response) => { - const res = response.data - const taskId = res.details.task_id + const data = response.data + const taskId = data.task_id dispatch(setScriptTaskId(taskId)) + + console.log('taskId2:', taskId) + setResult(data.output || 'No output available.') + setVariables(data.variables) + }) .catch(function (error) { console.error(error) @@ -388,11 +396,16 @@ export function ScriptScreen ({ isOpen, onClose }) { const executeScript = () => { dispatch(setScriptTaskId('')) prepareScriptNetlist(scriptDump) + dispatch(setShowDot(false)) } const resetCode = () => { dispatch(setSchScriptDump('')) - setResult('') + dispatch(setShowDot(false)) + dispatch(setScriptTaskId('')) + setResult('No output yet...') + setVariables('') + } return ( @@ -413,50 +426,190 @@ export function ScriptScreen ({ isOpen, onClose }) { <Box sx={{ p: 4 }}> {/* Code and Result Sections */} - <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 4, alignItems: 'stretch' }}> - {/* Scilab Code Input */} - <Box sx={{ p: 2, bgcolor: 'white', boxShadow: 2, borderRadius: 2, display: 'flex', flexDirection: 'column', height: '100%' }}> - <Typography variant='subtitle1' sx={{ fontWeight: 'bold', mb: 1 }}> + <Box + sx={{ + display: 'grid', + gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, + gap: 4, + alignItems: 'stretch', + }} + > + <Box + sx={{ + p: 2, + boxShadow: 2, + borderRadius: 2, + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + }} + > + <Typography variant="subtitle1" style={{ fontWeight: 'bold', mb: 1 }}> Scilab Code: </Typography> - <TextField - value={scriptDump} - onChange={scriptHandler} - multiline - minRows={12} - variant='outlined' - fullWidth - sx={{ fontFamily: 'Courier New, monospace', fontSize: '14px', flexGrow: 1 }} - /> + + <Box + sx={{ + height: '500px', + overflowY: 'scroll', + border: '1px solid #ccc', + borderRadius: 1, + '&::-webkit-scrollbar': { + width: '8px', + }, + '&::-webkit-scrollbar-track': { + backgroundColor: '#f1f1f1', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#888', + borderRadius: '4px', + }, + '&::-webkit-scrollbar-thumb:hover': { + backgroundColor: '#555', + }, + }} + > + <TextField + value={scriptDump} + onChange={scriptHandler} + multiline + variant="outlined" + fullWidth + InputProps={{ + disableUnderline: true, + sx: { + fontFamily: 'Courier New, monospace', + fontSize: '14px', + }, + }} + /> + </Box> </Box> - {/* Execution Result */} - <Box sx={{ p: 2, bgcolor: 'white', boxShadow: 2, borderRadius: 2, display: 'flex', flexDirection: 'column', height: '100%' }}> - <Typography variant='subtitle1' sx={{ fontWeight: 'bold', mb: 1 }}> + <Box + sx={{ + p: 2, + boxShadow: 2, + borderRadius: 2, + display: 'flex', + flexDirection: 'column', + height: '100%', + }} + > + <Typography variant="subtitle1" style={{ fontWeight: 'bold', mb: 1 }}> Result: </Typography> <Box sx={{ flexGrow: 1, width: '100%', - height: '200px', p: 2, border: '1px solid gray', borderRadius: 1, overflowY: 'auto', display: 'flex', - alignItems: 'center', - justifyContent: 'center', - minHeight: '200px' + height: '500px', + whiteSpace: 'pre-wrap', // Keep line breaks + '&::-webkit-scrollbar': { + width: '8px', + }, + '&::-webkit-scrollbar-track': { + backgroundColor: '#f1f1f1', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#888', + borderRadius: '4px', + }, + '&::-webkit-scrollbar-thumb:hover': { + backgroundColor: '#555', + }, }} > - {result || 'No output yet...'} + {result} + </Box> + + <Typography variant="subtitle1" style={{ fontWeight: "bold", marginTop: 16 }}> + Variable Browser : + </Typography> + <Box + style={{ + flexGrow: 1, + padding: 8, + border: "1px solid gray", + borderRadius: 4 + }} + > + <TableContainer + component={Paper} + elevation={0} + style={{ maxHeight: 150 }} + > + <Table size="small" stickyHeader> + <TableHead> + <TableRow style={{ backgroundColor: "#e0e0e0" }}> + <TableCell + style={{ + border: "1px solid gray", + fontWeight: "bold", + padding: "4px 8px", + }} + > + Name + </TableCell> + <TableCell + style={{ + border: "1px solid gray", + fontWeight: "bold", + padding: "4px 8px", + }} + > + Value + </TableCell> + <TableCell + style={{ + border: "1px solid gray", + fontWeight: "bold", + padding: "4px 8px", + }} + > + Type + </TableCell> + </TableRow> + </TableHead> + <TableBody> + {variables.length > 0 ? ( + variables.map((variable, index) => ( + <TableRow key={index}> + <TableCell style={{ border: "1px solid gray", padding: "4px 8px" }}> + {variable.name} + </TableCell> + <TableCell style={{ border: "1px solid gray", padding: "4px 8px" }}> + {variable.value} + </TableCell> + <TableCell style={{ border: "1px solid gray", padding: "4px 8px" }}> + {variable.type} + </TableCell> + </TableRow> + )) + ) : ( + <TableRow> + <TableCell colSpan={3} align="center"> + No variables available. + </TableCell> + </TableRow> + )} + </TableBody> + </Table> + </TableContainer> </Box> </Box> + </Box> + + {/* Action Buttons */} - <Box sx={{ mt: 4, display: 'flex', gap: 2 }}> + <Box sx={{ mt: 4, display: 'flex', gap: 4 }}> <Button onClick={executeScript} color='primary' variant='contained'> Execute </Button> diff --git a/blocks/eda-frontend/src/redux/saveSchematicSlice.js b/blocks/eda-frontend/src/redux/saveSchematicSlice.js index 639e130b..7cfdcf3c 100644 --- a/blocks/eda-frontend/src/redux/saveSchematicSlice.js +++ b/blocks/eda-frontend/src/redux/saveSchematicSlice.js @@ -8,6 +8,7 @@ const initialState = { description: '', xmlData: null, scriptDump: '', + showDot: false, details: {}, isLoading: false, isSaved: null, @@ -186,6 +187,9 @@ const saveSchematicSlice = createSlice({ }, setSchScriptDump: (state, action) => { state.scriptDump = action.payload + }, + setShowDot: (state, action) => { + state.showDot = action.payload } }, extraReducers: (builder) => { @@ -214,6 +218,7 @@ const saveSchematicSlice = createSlice({ state.title = action.payload.name state.xmlData = action.payload.data_dump state.scriptDump = action.payload.script_dump + state.showDot = action.payload.script_dump !== '' }) .addCase(fetchSchematic.rejected, (state) => { state.isLoading = false @@ -230,6 +235,7 @@ const saveSchematicSlice = createSlice({ state.title = action.payload.name state.xmlData = action.payload.data_dump state.scriptDump = action.payload.script_dump + state.showDot = action.payload.script_dump !== '' }) .addCase(fetchDiagram.rejected, (state) => { state.isLoading = false @@ -257,6 +263,7 @@ const saveSchematicSlice = createSlice({ state.title = action.payload.name state.xmlData = action.payload.data_dump state.scriptDump = action.payload.script_dump + state.showDot = action.payload.script_dump !== '' }) .addCase(openLocalSch.rejected, (state) => { state.isLoading = false @@ -269,7 +276,8 @@ export const { setSchTitle, setSchDescription, setSchXmlData, - setSchScriptDump + setSchScriptDump, + setShowDot } = saveSchematicSlice.actions export default saveSchematicSlice.reducer diff --git a/blocks/simulationAPI/helpers/config.py b/blocks/simulationAPI/helpers/config.py index 9bc8bce3..0db1da84 100644 --- a/blocks/simulationAPI/helpers/config.py +++ b/blocks/simulationAPI/helpers/config.py @@ -11,6 +11,8 @@ SCILAB_START_INSTANCES = int(os.environ.get('SCILAB_START_INSTANCES', '2')) SCILAB_MAX_INSTANCES = int(os.environ.get('SCILAB_MAX_INSTANCES', '3')) SCILAB_INSTANCE_RETRY_INTERVAL = int(os.environ.get('SCILAB_INSTANCE_RETRY_INTERVAL', '5')) +SCILAB_INSTANCE_TIMEOUT_INTERVAL = 300 + # Following are system command which are not permitted in sci files # (Reference scilab-on-cloud project) SYSTEM_COMMANDS = ( @@ -21,3 +23,8 @@ SPECIAL_CHARACTERS = r'["\'\\]' # The directory where images are created IMAGEDIR = 'images' + +# Set CREATEIMAGE to True to create img_test.jpg in IMAGEDIR +CREATEIMAGE = False + +REMOVEFILE = True diff --git a/blocks/simulationAPI/helpers/ngspice_helper.py b/blocks/simulationAPI/helpers/ngspice_helper.py index c5eb8e71..3b466760 100644 --- a/blocks/simulationAPI/helpers/ngspice_helper.py +++ b/blocks/simulationAPI/helpers/ngspice_helper.py @@ -13,6 +13,7 @@ from django.db.models import Case, F, Value, When from django.utils.timezone import now from simulationAPI.models import Task +from simulationAPI.helpers.scilab_manager import start_scilab, upload logger = get_task_logger(__name__) XmlToXcos = join(settings.BASE_DIR, 'Xcos/XmlToXcos.sh') @@ -50,8 +51,13 @@ class CannotRunParser(Exception): def update_task_status(task_id, status, meta=None): + print("status:", status, task_id) # Update Celery backend state - current_task.update_state(state=status, meta=meta or {}) + if current_task is not None: + try: + current_task.update_state(state=status, meta=meta or {}) + except Exception as e: + print(f"Error updating Celery task state: {e}") # Update Django database Task.objects.filter(task_id=task_id).update( @@ -110,74 +116,47 @@ def CreateXcos(file_path, parameters, task_id): return xcosfile -def ExecXml(task, task_name): +def ExecXml(task, task_name, workspace_file): task_id = task.task_id file_path = task.file.path + current_dir = settings.MEDIA_ROOT + '/' + str(task_id) try: + # Create xcos file xcosfile = CreateXml(file_path, task.parameters, task_id) - (logfilefd, log_name) = mkstemp(prefix=datetime.now().strftime( - 'scilab-log-%Y%m%d-'), suffix='.txt', dir=current_dir) - - if logfilefd != LOGFILEFD: - os.dup2(logfilefd, LOGFILEFD) - os.close(logfilefd) - - task.log_name = log_name - task.save() - - logger.info('will run %s %s> %s', SCILAB_CMD[0], LOGFILEFD, log_name) - logger.info('running command %s', SCILAB_CMD[-1]) - proc = subprocess.Popen( - SCILAB_CMD, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - start_new_session=True, universal_newlines=True, cwd=current_dir, - pass_fds=(LOGFILEFD, )) - - os.close(LOGFILEFD) - - update_task_status(task_id, 'STREAMING', - meta={'current_process': 'Processed Xml, Streaming Output'}) - - cmd = "try;" - cmd += "chdir('%s');" % current_dir - cmd += "loadXcosLibs();" - cmd += "importXcosDiagram('%s');" % xcosfile - cmd += "xcos_simulate(scs_m,4);" - cmd += SCILAB_END - - logger.info('running command %s', cmd) - proc.stdin.write(cmd) - - (out, err) = proc.communicate() - - maxlines = 15 - logger.info('Ran %s', SCILAB_CMD[0]) - if out: - out = out.strip() - if out: - out = '\n'.join(re.split(r'\n+', out, maxlines + 1)[:maxlines]) - logger.info('out=%s', out) - if err: - err = re.sub(r'Undefined variable: helpbrowser_update', '', err) - err = err.strip() - if err: - err = '\n'.join(re.split(r'\n+', err, maxlines + 1)[:maxlines]) - logger.info('err=%s', err) - - task.returncode = proc.returncode - task.save() - - return 'Streaming' + + upload(task.session, task, xcosfile) + result = start_scilab(task.session, task, xcosfile) + + if result == "": + logger.info('Simulation completed successfully for task %s', task_id) + update_task_status(task_id, 'SUCCESS', + meta={'current_process': 'Simulation Completed'}) + return 'Streaming' + else: + logger.warning('Simulation failed for task %s: %s', task_id, result) + update_task_status(task_id, 'FAILURE', + meta={'current_process': result}) + return 'Failure' + except BaseException as e: - logger.exception('Encountered Exception:') - logger.info('removing %s', file_path) - os.remove(file_path) + logger.exception('Encountered Exception during XML Execution:') + logger.info('Cleaning up files for task %s', task_id) + # Cleanup + try: + os.remove(file_path) + except FileNotFoundError: + pass target = os.listdir(current_dir) for item in target: - logger.info('removing %s', item) - os.remove(join(current_dir, item)) - logger.info('removing %s', current_dir) - os.rmdir(current_dir) - logger.info('Deleted Files') + try: + os.remove(join(current_dir, item)) + except FileNotFoundError: + continue + try: + os.rmdir(current_dir) + except OSError: + pass + logger.info('Deleted Files and Directory for task %s', task_id) raise e + diff --git a/blocks/simulationAPI/helpers/scilab_manager.py b/blocks/simulationAPI/helpers/scilab_manager.py index 79000d91..fb693167 100644 --- a/blocks/simulationAPI/helpers/scilab_manager.py +++ b/blocks/simulationAPI/helpers/scilab_manager.py @@ -7,8 +7,9 @@ from gevent.event import Event from gevent.lock import RLock import glob import json +import fileinput import os -from os.path import abspath, exists, isfile, join +from os.path import abspath, exists, isfile, join, splitext import re import signal import subprocess @@ -17,13 +18,18 @@ from threading import current_thread from time import time import unicodedata import uuid +import logging +from xml.dom import minidom +import shutil from simulationAPI.helpers import config + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blocks.settings') # Scilab dir SCILAB_DIR = abspath(settings.SCILAB_DIR) +READCONTENTFILE = abspath("resources/Read_Content.txt") SCILAB = join(SCILAB_DIR, 'bin', 'scilab-adv-cli') BASEDIR = abspath('src/static') IMAGEDIR = join(BASEDIR, config.IMAGEDIR) @@ -66,7 +72,8 @@ SCILAB_CMD = [SCILAB, "-nouserstartup", "-nb", "-nw", - "-e", SCILAB_START] + "-e", SCILAB_START + ] USER_DATA = {} @@ -222,7 +229,7 @@ class UserData: def __init__(self): self.sessiondir = mkdtemp( prefix=datetime.now().strftime('%Y%m%d.'), dir=SESSIONDIR) - self.diagrams = [] + self.diagrams = {} self.datafiles = [] self.scripts = {} self.scriptcount = 0 @@ -230,6 +237,18 @@ class UserData: self.diagramlock = RLock() self.timestamp = time() + def __str__(self): + return (f"UserData(sessiondir={self.sessiondir}, " + f"diagrams={len(self.diagrams)}, " + f"datafiles={len(self.datafiles)}, " + f"scripts={list(self.scripts.keys())}, " + f"scriptcount={self.scriptcount}, " + f"scifile={self.scifile}, " + f"timestamp={self.timestamp})") + + def __repr__(self): + return self.__str__() + def getscriptcount(self): with self.diagramlock: rv = self.scriptcount @@ -238,7 +257,7 @@ class UserData: return str(rv) def clean(self): - for diagram in self.diagrams: + for diagram in self.diagrams.values(): diagram.clean() self.diagrams = None for script in self.scripts: @@ -615,15 +634,22 @@ def prestart_scilab(): return (proc, log_name) -def run_scilab(command, base, createlogfile=False, timeout=70): +def run_scilab(command, base, createlogfile=False, timeout=1800): instance = get_scilab_instance() if instance is None: logger.error('cannot run command %s', command) return None - cmd = command + SCILAB_END + logger.info('Scilab instance log file: %s', instance.log_name) + cmd = 'try;' + command + SCILAB_END + '\n' logger.info('running command %s', cmd) instance.proc.stdin.write(cmd) + instance.proc.stdin.flush() + + # output, error = instance.proc.communicate(timeout=timeout) + # with open(instance.log_name, 'a') as log: + # log.write(output if output else '') + # log.write(error if error else '') if not createlogfile: remove(instance.log_name) @@ -673,17 +699,20 @@ def uploadscript(session, task): ''' Below route is called for uploading script file. ''' - (script, sessiondir) = add_script(session) + (script, sessiondir) = add_script(session, task) - file = task.file.name + file = task.file if not file: msg = "Upload Error\n" rv = {'msg': msg} - return JsonResponse(rv) + return rv fname = join(sessiondir, SCRIPT_FILES_FOLDER, - script.script_id + '_script.sce') - file.save(fname) + f"{script.script_id}_script.sce") + # file.save(fname) + with open(fname, 'wb+') as destination: + for chunk in file.chunks(): + destination.write(chunk) script.filename = fname if is_unsafe_script(fname): @@ -691,25 +720,30 @@ def uploadscript(session, task): "Please edit the script again.\n") script.status = -1 rv = {'status': script.status, 'msg': msg} - return JsonResponse(rv) + return rv - wfname = join(sessiondir, SCRIPT_FILES_FOLDER, - script.script_id + '_script_workspace.dat') + wfname = join(sessiondir, WORKSPACE_FILES_FOLDER, + f"{script.script_id}_script_workspace.dat") script.workspace_filename = wfname command = "exec('%s');save('%s');" % (fname, wfname) script.instance = run_scilab(command, script) + if script.instance is None: msg = "Resource not available" script.status = -2 rv = {'status': script.status, 'msg': msg} - return JsonResponse(rv) + return rv + + # Save workspace file in task model + task.workspace_file = wfname + task.save() msg = '' script.status = 1 - rv = {'script_id': script.script_id, 'status': script.status, 'msg': msg} - return JsonResponse(rv) + rv = {'task_id': task.task_id, 'script_id': script.script_id, 'status': script.status, 'msg': msg} + return rv def clean_output(s): @@ -724,24 +758,24 @@ def clean_output(s): return s -def getscriptoutput(session): +def getscriptoutput(session, task): ''' Below route is called for uploading script file. ''' - script = get_script(session, get_script_id()) + script = get_script(session, task) if script is None: # when called with same script_id again or with incorrect script_id logger.warning('no script') msg = "no script" rv = {'msg': msg} - return JsonResponse(rv) + return rv instance = script.instance if instance is None: logger.warning('no instance') msg = "no instance" rv = {'msg': msg} - return JsonResponse(rv) + return rv proc = instance.proc @@ -758,7 +792,7 @@ def getscriptoutput(session): msg = 'Script stopped' script.status = -5 rv = {'status': script.status, 'msg': msg, 'output': output} - return JsonResponse(rv) + return rv if returncode > 0: logger.info('return code is %s', returncode) if output: @@ -771,7 +805,7 @@ def getscriptoutput(session): "Please edit the script and execute again.\n") script.status = -3 rv = {'status': script.status, 'msg': msg, 'output': output} - return JsonResponse(rv) + return rv logger.info('workspace for %s saved in %s', script.script_id, script.workspace_filename) @@ -786,7 +820,7 @@ def getscriptoutput(session): msg = "Resource not available" script.status = -2 rv = {'status': script.status, 'msg': msg} - return JsonResponse(rv) + return rv proc = instance.proc listoutput = proc.communicate(timeout=10)[0] @@ -799,7 +833,7 @@ def getscriptoutput(session): msg = 'Script stopped' script.status = -5 rv = {'status': script.status, 'msg': msg, 'output': listoutput} - return JsonResponse(rv) + return rv if returncode > 0: logger.info('return code is %s', returncode) if listoutput: @@ -815,26 +849,26 @@ def getscriptoutput(session): rv = {'script_id': script.script_id, 'status': script.status, 'msg': msg, 'output': output, 'returncode': returncode, 'variables': variables} - return JsonResponse(rv) + return rv except subprocess.TimeoutExpired: kill_script(script) msg = 'Timeout' script.status = -4 rv = {'status': script.status, 'msg': msg} - return JsonResponse(rv) + return rv except UnicodeDecodeError: kill_script(script) msg = 'Unicode Decode Error' script.status = -6 rv = {'status': script.status, 'msg': msg} - return JsonResponse(rv) + return rv -def sendfile(session): +def sendfile(session, task): ''' This route is used in chart.js for sending image filename ''' - diagram = get_diagram(session, get_request_id()) + diagram = get_diagram(session, task) if diagram is None: logger.warning('no diagram') return '' @@ -916,12 +950,12 @@ def load_variables(filename): return command -def start_scilab(session): +def start_scilab(session, task, xcosfile): ''' function to execute xcos file using scilab (scilab-adv-cli), access log file written by scilab ''' - diagram = get_diagram(session, get_request_id()) + diagram = get_diagram(session, task) if diagram is None: logger.warning('no diagram') return "error" @@ -1001,6 +1035,8 @@ def start_scilab(session): instance = diagram.instance logger.info('log_name=%s', instance.log_name) + task.log_name = instance.log_name + task.save() # Start sending log to chart function for creating chart try: @@ -1069,15 +1105,313 @@ def stopDetailsThread(diagram): remove(fn) -def get_diagram(session, xcos_file_id, remove=False): - if not xcos_file_id: +def upload(session, task, xcosfile): + '''Route that will process the file upload''' + # Get the file + file = xcosfile + # Check if the file is not null + if not file: + return "error" + # flags to check if both TOWS_c and FROMWSB are present + flag1 = 0 + flag2 = 0 + list1 = [] + list2 = [] + # Make the filename safe, remove unsupported chars + (diagram, scripts, sessiondir) = add_diagram(session, task) + + script = get_script(session, task, scripts=scripts) + if script is not None: + diagram.workspace_filename = script.workspace_filename + # Save the file in xml extension and using it for further modification + # by using xml parser + temp_file_xml_name = diagram.diagram_id + ".xml" + shutil.copy(xcosfile, temp_file_xml_name) + # file.save(temp_file_xml_name) + new_xml = minidom.parse(temp_file_xml_name) + + # to identify if we have to load or save to workspace or neither #0 if + # neither TOWS_c or FROMWSB found + blocks = new_xml.getElementsByTagName("BasicBlock") + tk_is_present = False + pattern = re.compile(r"<SplitBlock") + for i, line in enumerate(open(temp_file_xml_name)): + for match in re.finditer(pattern, line): + list1.append(i + 1) + pattern1 = re.compile(r"<ControlPort") + for i, line in enumerate(open(temp_file_xml_name)): + for match in re.finditer(pattern1, line): + list2.append(i + 1) + pattern2 = re.compile(r"<ImplicitInputPort") + count1 = 0 + + for i, line in enumerate(open(temp_file_xml_name)): + for match in re.finditer(pattern2, line): + count1 += 1 + if count1 >= 1: + splitline = [] + count = 0 + for i in range(len(list1)): + for j in range(len(list2)): + if list2[j] == list1[i] + 3: + count += 1 + splitline.append(list1[i]) + blocksplit = new_xml.getElementsByTagName("SplitBlock") + block_ids = [] # this stores the id of split blocks + for block in blocksplit: + if block.getAttribute("style") == "SPLIT_f": + block_ids.append(int(block.getAttribute("id"))) + compsplit = [] + for i in range(len(splitline)): + for j in range(len(list1)): + if splitline[i] == list1[j]: + compsplit.append(j) + + finalsplit = [] + for i in range(len(compsplit)): + finalsplit.append(block_ids[compsplit[i]]) + + blockcontrol = new_xml.getElementsByTagName("ControlPort") + for block in blockcontrol: + for i in range(len(finalsplit)): + # match the lines with the parent of our spliblocks which + # we need to change + if block.getAttribute("parent") == str(finalsplit[i]): + block.setAttribute('id', '-1') + + blockcommand = new_xml.getElementsByTagName("CommandPort") + for block in blockcommand: + for i in range(len(finalsplit)): + if block.getAttribute("parent") == str(finalsplit[i]): + block.setAttribute('id', '-1') + + # here we take the ids of command controllink which we will search + # and change + finalchangeid = [] + for i in range(len(finalsplit)): + finalchangeid.append(finalsplit[i] + 4) + finalchangeid.append(finalsplit[i] + 5) + + # here we save the contents + with open(temp_file_xml_name, 'w') as f: + f.write(new_xml.toxml()) + + with open(temp_file_xml_name, "r") as f: + newline = [] + i = 0 + for word in f.readlines(): + + if "<CommandControlLink id=" in word: + temp_word = "" + for i in range(len(finalchangeid)): + fcid = str(finalchangeid[i]) + srch = '<CommandControlLink id="' + fcid + '"' + if srch in word: + rplc = '<ImplicitLink id="' + fcid + '"' + temp_word = word.replace(srch, rplc) + i += 1 + if temp_word != "": + newline.append(temp_word) + else: + newline.append(word) + else: + newline.append(word) + with open(temp_file_xml_name, "w") as f: + for line in newline: + f.writelines(line) + with open(temp_file_xml_name, "r") as in_file: + buf = in_file.readlines() + # length=len(finalsplit) + # return finalsplit + with open(temp_file_xml_name, "w") as out_file: + for line in buf: + for i in range(len(finalsplit)): + fs = str(finalsplit[i]) + srch = ('<ControlPort connectable="0" ' + 'dataType="UNKNOW_TYPE" id="-1" ordering="1" ' + 'parent="' + fs + '"') + if srch in line: + line = ( + '\t <ImplicitInputPort connectable="0" ' + 'dataType="UNKNOW_TYPE" ' + 'id="' + str(finalsplit[i] + 1) + '" ' + 'ordering="1" parent="' + fs + '" ' + 'style="ImplicitInputPort">\n' + '\t\t<mxGeometry as="geometry" height="10" ' + 'relative="1" width="10" y="0.5000">\n' + '\t\t</mxGeometry>\n' + '\t </ImplicitInputPort>\n' + '\t <ImplicitOutputPort connectable="0" ' + 'dataType="UNKNOW_TYPE" ' + 'id="' + str(finalsplit[i] + 2) + '" ' + 'ordering="1" parent="' + fs + '" ' + 'style="ImplicitOutputPort">\n' + '\t\t<mxGeometry as="geometry" height="10" ' + 'relative="1" width="10" y="0.5000">\n' + '\t\t</mxGeometry>\n' + '\t </ImplicitOutputPort>\n' + '\t <ImplicitOutputPort connectable="0" ' + 'dataType="UNKNOW_TYPE" ' + 'id="' + str(finalsplit[i] + 3) + '" ' + 'ordering="1" parent="' + fs + '" ' + 'style="ImplicitOutputPort">\n' + '\t\t<mxGeometry as="geometry" height="10" ' + 'relative="1" width="10" y="0.5000">\n' + '\t\t</mxGeometry>\n' + '\t </ImplicitOutputPort>\n' + line) + + out_file.write(line) + list3 = [] + implitdetect = [] + # return temp_file_xml_name + for i in range(len(finalsplit)): + implitdetect.append(finalsplit[i] + 5) + implitdetect.append(finalsplit[i] + 6) + for i in range(len(implitdetect)): + pattern3 = re.compile( + "<ImplicitLink id=\"" + str(implitdetect[i]) + "\"") + for i, line in enumerate(open(temp_file_xml_name)): + for match in re.finditer(pattern3, line): + list3.append(i - 1) + with open(temp_file_xml_name, 'r+') as f: + data = f.read().splitlines() + replace = list3 + for i in replace: + data[i] = '\t </ImplicitLink>' + f.seek(0) + f.write('\n'.join(data)) + f.truncate() + fname = join(sessiondir, UPLOAD_FOLDER, + splitext(temp_file_xml_name)[0] + ".xcos") + os.rename(temp_file_xml_name, fname) + diagram.xcos_file_name = fname + return diagram.diagram_id + + # List to contain all affich blocks + blockaffich = new_xml.getElementsByTagName("AfficheBlock") + for block in blockaffich: + interfaceFunctionName = block.getAttribute("interfaceFunctionName") + if interfaceFunctionName == "AFFICH_m": + diagram.workspace_counter = 4 + + # List to contain all the block IDs of tkscales so that we can create + # read blocks with these IDs + block_id = [] + for block in blocks: + interfaceFunctionName = block.getAttribute("interfaceFunctionName") + if interfaceFunctionName == "TKSCALE": + block_id.append(block.getAttribute("id")) + block.setAttribute('id', '-1') + tk_is_present = True + # Changed the ID of tkscales to -1 so that virtually the + # tkscale blocks get disconnected from diagram at the backend + # Taking workspace_counter 1 for TOWS_c and 2 for FROMWSB + elif interfaceFunctionName == "scifunc_block_m": + diagram.workspace_counter = 5 + elif interfaceFunctionName == "TOWS_c": + if block.childNodes: + for node in block.childNodes: + if not isinstance(node, minidom.Element): + continue + if node.getAttribute("as") != "exprs": + continue + if node.childNodes is None: + continue + childCount = 0 + for childChildNode in node.childNodes: + if not isinstance(childChildNode, minidom.Element): + continue + childCount += 1 + if childCount != 2: + continue + value = childChildNode.getAttribute("value") + if value is not None: + diagram.save_variables.add(value) + break + diagram.workspace_counter = 1 + flag1 = 1 + elif interfaceFunctionName == "FROMWSB": + diagram.workspace_counter = 2 + flag2 = 1 + if diagram.save_variables: + logger.info("save variables = %s", diagram.save_variables) + if flag1 and flag2: + # Both TOWS_c and FROMWSB are present + diagram.workspace_counter = 3 + # Hardcoded the real time scaling to 1.0 (i.e., no scaling of time + # occurs) only if tkscale is present + if tk_is_present: + for dia in new_xml.getElementsByTagName("XcosDiagram"): + dia.setAttribute('realTimeScaling', '1.0') + + # Save the changes made by parser + with open(temp_file_xml_name, 'w') as f: + f.write(new_xml.toxml()) + + # In front of block tkscale printing the block corresponding to read + # function and assigning corresponding values + skipblock = False + for line in fileinput.input(temp_file_xml_name, inplace=1): + + if 'interfaceFunctionName=\"TKSCALE\"' in line: + # change the block ID + i = diagram.tk_count + print('<BasicBlock blockType="d" id="', block_id[i], '" ' + 'interfaceFunctionName="RFILE_f" parent="1" ' + 'simulationFunctionName="readf" ' + 'simulationFunctionType="DEFAULT" style="RFILE_f">', + sep='') + print('<ScilabString as="exprs" height="5" width="1">') + print('<data column="0" line="0" value="1"/>') + # Value equal to 1 implies take readings from first column in + # the file + print('<data column="0" line="1" value="2"/>') + # Path to the file from which read block obtains the values + fname = join(diagram.sessiondir, VALUES_FOLDER, + diagram.diagram_id + "_tk" + str(i + 1) + ".txt") + print('<data column="0" line="2" value="', fname, '"/>', + sep='') + print('<data column="0" line="3" value="(2(e10.3,1x))"/>') + # (2(e10.3,1x)) The format in which numbers are written + # Two columns with base 10 and 3 digits after decimal and 1x + # represents 1 unit space between two columns. + print('<data column="0" line="4" value="2"/>') + print('</ScilabString>') + print('<ScilabDouble as="realParameters" ' + 'height="0" width="0"/>') + print('<ScilabDouble as="integerParameters" ' + 'height="105" width="1">') + diagram.tk_count += 1 + # The remaining part of the block is read from the + # Read_Content.txt file and written to the xml file + with open(READCONTENTFILE, "r") as read_file: + for line_content in read_file: + print(line_content, end='') + skipblock = True + elif skipblock: + if '</BasicBlock>' in line: + skipblock = False + else: + print(line, end='') + + # Changing the file extension from xml to xcos + fname = join(sessiondir, UPLOAD_FOLDER, + splitext(temp_file_xml_name)[0] + ".xcos") + # Move the xcos file to uploads directory + os.rename(temp_file_xml_name, fname) + diagram.xcos_file_name = fname + return diagram.diagram_id + + +def get_diagram(session, task, remove=False): + if not task: logger.warning('no id') return None - xcos_file_id = int(xcos_file_id) + xcos_file_id = task.task_id (diagrams, __, __, __, __, __, __) = init_session(session) - if xcos_file_id < 0 or xcos_file_id >= len(diagrams): + if xcos_file_id not in diagrams: logger.warning('id %s not in diagrams', xcos_file_id) return None @@ -1089,49 +1423,45 @@ def get_diagram(session, xcos_file_id, remove=False): return diagram -def add_diagram(session): +def add_diagram(session, task): (diagrams, scripts, __, __, __, sessiondir, diagramlock) = init_session(session) with diagramlock: diagram = Diagram() diagram.diagram_id = str(len(diagrams)) diagram.sessiondir = sessiondir - diagrams.append(diagram) + # diagrams.append(diagram) + diagrams[task.task_id] = diagram return (diagram, scripts, sessiondir) -def get_script(session, script_id, scripts=None, remove=False): - if script_id is None: - return None - if not script_id: - logger.warning('no id') +def get_script(session, task, scripts=None, remove=False): + if task is None: return None if scripts is None: (__, scripts, __, __, __, __, __) = init_session(session) - if script_id not in scripts: - logger.warning('id %s not in scripts', script_id) + if task.task_id not in scripts: + logger.warning('id %s not in scripts', task.task_id) return None - script = scripts[script_id] + script = scripts[task.task_id] if remove: - del scripts[script_id] + del scripts[task.task_id] return script -def add_script(session): - (__, scripts, getscriptcount, __, __, sessiondir, __) = init_session(session) - - script_id = getscriptcount() +def add_script(session, task): + (__, scripts, __, __, __, sessiondir, __) = init_session(session) script = Script() - script.script_id = script_id + script.script_id = task.task_id script.sessiondir = sessiondir - scripts[script_id] = script + scripts[task.task_id] = script return (script, sessiondir) @@ -1192,24 +1522,6 @@ def get_request_id(request, key='id'): return '' -def get_script_id(request, key='script_id', default=''): - form = request.form - if form is None: - logger.warning('No form in request') - return default - if key not in form: - logger.warning('No %s in request.form', key) - return default - value = form[key] - if re.fullmatch(r'[0-9]+', value): - return value - displayvalue = value if len( - value) <= DISPLAY_LIMIT + 3 else value[:DISPLAY_LIMIT] + '...' - logger.warning('Invalid value %s for %s in request.form', - displayvalue, key) - return default - - def internal_fun(session, task, internal_key): (__, __, __, scifile, __, sessiondir, __) = init_session(session) @@ -1310,10 +1622,10 @@ def clean_text_2(s, forindex): return s -def kill_scilab(diagram=None, session=None): +def kill_scilab(diagram=None, session=None, task=None): '''Define function to kill scilab(if still running) and remove files''' if diagram is None: - diagram = get_diagram(session, get_request_id(), True) + diagram = get_diagram(session, task, True) if diagram is None: logger.warning('no diagram') @@ -1335,10 +1647,10 @@ def kill_scilab(diagram=None, session=None): stopDetailsThread(diagram) -def kill_script(script=None, session=None): +def kill_script(script=None, session=None, task=None): '''Below route is called for stopping a running script file.''' if script is None: - script = get_script(session, get_script_id(), remove=True) + script = get_script(session, task, remove=True) if script is None: # when called with same script_id again or with incorrect script_id logger.warning('no script') diff --git a/blocks/simulationAPI/tasks.py b/blocks/simulationAPI/tasks.py index e13c73bb..5d18d789 100644 --- a/blocks/simulationAPI/tasks.py +++ b/blocks/simulationAPI/tasks.py @@ -8,6 +8,7 @@ import traceback from blocks.celery_tasks import app from simulationAPI.helpers.ngspice_helper import ExecXml, update_task_status from simulationAPI.models import Task +from simulationAPI.helpers.scilab_manager import uploadscript, getscriptoutput logger = get_task_logger(__name__) @@ -27,26 +28,35 @@ def release_lock(lock): def process_task(self, task_id): task = Task.objects.get(task_id=task_id) session_id = task.session.session_id + task_type = task.type current_thread().name = f"{session_id[:6]}:{task_id[:8]}" lock = acquire_lock(session_id) # Prevent multiple runs per session try: - logger.info("Processing %s %s %s", - task_id, task.file.path, task.session.app_name) + logger.info("Processing %s %s %s %s", + task_id, task.file.path, task.session.app_name, task.workspace_file) update_task_status(task_id, 'STARTED', meta={'current_process': 'Started Processing File'}) - output = ExecXml(task, self.name) - if output == "Streaming": - state = 'STREAMING' - current_process = 'Processed Xml, Streaming Output' - elif output == "Success": + if task_type == 'SCRIPT': + output = uploadscript(task.session, task) + output = getscriptoutput(task.session, task) state = 'SUCCESS' - current_process = 'Processed Xml, Loading Output' + update_task_status(task_id, state, + meta=output) + else: + output = ExecXml(task, self.name, task.workspace_file) + if output == "Streaming": + state = 'STREAMING' + current_process = 'Processed Xml, Streaming Output' + elif output == "Success": + state = 'SUCCESS' + current_process = 'Processed Xml, Loading Output' + + update_task_status(task_id, state, + meta={'current_process': current_process}) - update_task_status(task_id, state, - meta={'current_process': current_process}) return output except Exception as e: @@ -60,3 +70,4 @@ def process_task(self, task_id): finally: release_lock(lock) # Ensure lock is always released + diff --git a/blocks/simulationAPI/urls.py b/blocks/simulationAPI/urls.py index 5e37d066..1714a768 100644 --- a/blocks/simulationAPI/urls.py +++ b/blocks/simulationAPI/urls.py @@ -18,4 +18,5 @@ urlpatterns = [ path('cancel/<uuid:task_id>', simulationAPI_views.CancelTaskView.as_view(), name='cancel'), path('get_session', simulationAPI_views.get_session, name='get_session'), + ] diff --git a/blocks/simulationAPI/views.py b/blocks/simulationAPI/views.py index dd1a7758..625aa774 100644 --- a/blocks/simulationAPI/views.py +++ b/blocks/simulationAPI/views.py @@ -17,7 +17,8 @@ from simulationAPI.models import Task, Session from simulationAPI.negotiation import IgnoreClientContentNegotiation from simulationAPI.serializers import TaskSerializer from simulationAPI.tasks import process_task -from simulationAPI.helpers.ngspice_helper import CreateXcos +from simulationAPI.helpers.ngspice_helper import CreateXcos, update_task_status + SCILAB_INSTANCE_TIMEOUT_INTERVAL = 300 @@ -63,15 +64,35 @@ class XmlUploader(APIView): serializer = TaskSerializer(data=request.data, context={'request': request}) if serializer.is_valid(): - serializer.save() + task = serializer.save() + + task_type = request.data.get('type') + script_task_id = request.data.get('script_task_id') + + if task_type == 'XCOS' and script_task_id: + try: + script_task = Task.objects.get(task_id=script_task_id, type='SCRIPT') + if script_task.workspace_file: + task.workspace_file = script_task.workspace_file + task.save() + logger.info(f'Copied workspace file from script task {script_task_id} to xcos task {task.task_id}') + else: + logger.warning(f'Script task {script_task_id} does not have a workspace file') + except Task.DoesNotExist: + logger.warning(f'Script task {script_task_id} not found') + # serializer.save() task_id = serializer.data['task_id'] celery_task = process_task.apply_async( kwargs={'task_id': str(task_id)}, task_id=str(task_id)) - # celery_task = process_task.delay(str(task_id)) - response_data = { - 'state': celery_task.state, - 'details': serializer.data, - } + if task_type == 'XCOS': + response_data = { + 'state': celery_task.state, + 'details': serializer.data, + } + else: + rv = celery_task.get(timeout=10) + response_data = {**rv, 'task_id':task_id} + return Response(response_data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -349,6 +370,7 @@ class StreamView(APIView): else: logger.info('lines = %s, log size = %s', lineno, log_size) + update_task_status(task_id, 'SUCCESS') # Notify Client yield "event: DONE\ndata: None\n\n" |