python工業網際網路應用實戰6—任務分解
根據需求定義“任務”是一個完整的業務搬運流程,整個流程涉及到多個機構(裝置)分別動作執行多個步驟,所以依據前面的模型設計,需要把任務分解到多個連續的子任務(作業),未來通過順序串聯下達執行的方式來分步驟的完成任務的執行。
1.1. 倉庫規劃
同樣,依據需求我們先來做幾個倉庫的規劃設計,101位置是AGV的入庫起始站臺,102是提升機門口的入庫工位,104是提升機1樓轎廂工位,同理504是提升機在5樓的轎廂工位。左邊是5樓的貨位區編碼規則,完整好的貨位號加上樓層編碼05-01-01表示五樓的01-01貨位,如下圖:
有了圖上的基本倉庫規劃設計,我們才能把一個任務依據設計進行分解,例如:任務編碼100的搬運任務是把貨物從101 入庫工位搬運到05-01-01貨位存放。
設計約定:
① 102、502為進提升機工位,103、503為出提升機工位,出和入的工位分開來解決任務衝突的問題(當然也可以先規劃一個工位,隨著程式迭代改進)。
② 提升機控制邏輯與電梯類似,到達指定樓層後會自動開門。
③ 電梯只有關上廊門才能提升/下降。
這樣根據約定(前置條件)現在我們針對這個任務逐步分解成以下子任務(作業)如下表,對照早期的需求會發現有增加作業,實際專案中也是如此,早期的需求定義到實際編碼的時候,需要根據實際的裝置介面協議,規定等調整早期的需求定義。
序號 |
作業描述 |
執行裝置 |
1 |
排程AGV從101站臺搬運托盤到102站臺 |
1樓AGV |
2 |
排程提升機到1樓並開啟門 |
提升機 |
3 |
排程AGV從102工位搬運到104工位並卸貨 |
1樓AGV |
4 |
排程空AGV從104工位到103工位 |
1樓AGV |
5 |
排程提升機關門 |
提升機 |
6 |
排程提升機1樓提升到5樓並開門 |
提升機 |
7 |
排程空AGV到504工位並載貨 |
5樓AGV |
8 |
排程AGV從504工位到503工位 |
5樓AGV |
9 |
排程提升機關門 |
提升機 |
10 |
排程AGV從503工位搬運到05-01-01貨位並卸貨 |
5樓AGV |
上面的分解比需求表增加了兩個子任務“排程空AGV從104工位到102工位”和“排程AGV從504工位到502工位”,是與實際的AGV裝置對接通訊協議後增加的,AGV小車的控制系統不能識別“排程AGV從提升機1樓門口工位到提升機並卸貨,返回提升機門口工位”需求裡“返回”提升機門口工位。依據裝置的介面邏輯,只能把這個步驟再分解成兩個步驟來執行。
上述分解提供一個簡化分解的模型和思路,實際專案可能更為複雜或簡單。
1.2. 分解程式碼實現
我們在後臺任務管理裡新增任務編碼100、源地址101、目標地址05-01-01的任務,如下圖:
同時admin.py裡增加新增任務預設狀態設定為未處理的程式碼,我們通過pk值是否為None來判斷model是否是新增還是修改。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... def save_model(self, request, obj, form, change): #新增任務預設狀態設定為 未處理 if obj.pk==None: obj.State=1 obj.User=request.user return super().save_model(request, obj, form, change)
接下來,我們演示如何依據上面的分解邏輯用程式碼來實現把任務分解成子任務並儲存到Job對應的表裡,同時,把任務的狀態從“未處理”更新到“處理完成”狀態。這裡需要注意的就是任務的分解和狀態變更必須在一個事務裡完成,系統不能存在任務狀態已經變更到處理完成,但是沒有對應的作業,也不能存在已有對應的作業任務狀態仍然是未處理狀態的情形,否則就會造成系統業務上的混亂,前面章節提到的事務應用就非常重要!
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): ... @atomic def task_decompose_action(self, request, queryset): for obj in queryset: #只處理狀態等於未處理的任務 if obj.State==1: result=self.task_decompose(request,obj) if result: self.message_user(request, str(obj.TaskNum) + " 處理成功.") else: self.message_user(request, str(obj.TaskNum) + " 處理成功.") task_decompose_action.short_description = '處理所選的' + ' 任務' def task_decompose(self,request,obj): success=True try: job1=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":1,"Source":obj.Source,"Target":'102',"Executor":"AGV01","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job1.save() job2=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":2,"Source":None,"Target":'1',"Executor":"ELEVATOR","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job2.save() job3=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":3,"Source":"102","Target":'104',"Executor":"AGV01","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job3.save() job4=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":4,"Source":'104',"Target":'103',"Executor":"AGV01","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job4.save() job5=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":5,"Source":'1',"Target":'5',"Executor":"ELEVATOR","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job5.save() job6=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":6,"Source":'504',"Target":'503',"Executor":"AGV05","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job6.save() job7=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":7,"Source":'5',"Target":None,"Executor":"ELEVATOR","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job7.save() job8=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":8,"Source":'503',"Target":'05-01-01',"Executor":"AGV05","State":1,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job8.save() #子任務分解完成,並提交資料庫 #更新任務的狀態到“處理完成” obj.State=5 obj.save() except Exception: success = False return success
執行效果如下圖:
1.3. 列表增加“作業數量”
任務分解完成後,列表除了顯示狀態變化之外,能夠檢視作業的分解數量,為此在Task Admin裡增加job_count函式用來顯示作業數量。
#Task模型的管理器 class TaskAdmin(admin.ModelAdmin): #listdisplay設定要顯示在列表中的欄位(TaskId欄位是Django模型的主鍵) list_display = ('TaskId','TaskNum', 'Source', 'Target', 'Barcode','State','PriorityColor','BeginDate','EndDate','job_count','task_operate',) ... def job_count(self, obj): return obj.job_set.count() job_count.short_description = '作業數量'
1.4. 編輯頁面檢視作業資料
Job模型外來鍵管理Task模型,我就可以採用TabularInline的方式來顯示任務的作業資訊,通過編輯介面直接檢視對應任務的Job列表。
class JobInline(admin.TabularInline): model = Job fields = ('TaskNum','OrderNo','Source', 'Target','Executor','State','BeginDate','EndDate',) extra = 0 #預設所有條目 # 只讀的欄位 readonly_fields = ('TaskNum','OrderNo','Source', 'Target','Executor','State','BeginDate','EndDate',) def has_delete_permission(self, request, obj=None): return False #不允許刪除 def has_add_permission(self, request, obj=None): return False #不允許新增 #Task模型的管理器 class TaskAdmin(admin.ModelAdmin): #listdisplay設定要顯示在列表中的欄位(TaskId欄位是Django模型的主鍵) list_display = ('TaskId','TaskNum', 'Source', 'Target', 'Barcode','State','PriorityColor','BeginDate','EndDate','job_count','task_operate',) inlines=[JobInline] fieldsets = (("任務", {'fields': ['TaskNum', ('Source', 'Target'), 'Barcode','Priority','State','BeginDate','EndDate','User']}),)
1.5. 魔法數(mogic number)
任務的狀態賦值和變更我們採用了直接obj.State=1或obj.State=5的直接賦值數字的方式,這個就是程式的mogic number它們有特殊的含義,程式設計人員得知道這個數字對應的含義,當狀態變多時間拉長,程式設計人員就得去回憶或來回檢視數字對應的狀態。好的程式設計習慣就是用常量來代替魔法數字。重構models.py裡的Task和Job程式碼用常量替換mogic number.
class Task(models.Model): STATE_NEW =1 STATE_PROCESSED=4 STATE_RUNNING=5 STATE_COMPLETED=99 STATE_CANCEL=-1 TASK_STATE=((STATE_NEW,u'未處理'),(STATE_PROCESSED,u'處理成功'),(STATE_RUNNING,u'執行中'),(STATE_COMPLETED,u'完成'),(STATE_CANCEL,u'已取消')) TaskId = models.AutoField(u'ID',primary_key=True, db_column='task_id') TaskNum = models.IntegerField(u'任務號', null=False, db_column='task_num') Source = models.CharField(u'源地址', null=False, max_length=50, db_column='source') Target = models.CharField(u'目標地址', null=False, max_length=50, db_column='target') Barcode = models.CharField(u'容器條碼', null=False, max_length=50, db_column='barcode') State = models.IntegerField(u'狀態', choices=TASK_STATE, null=False, db_column='state') Priority = models.IntegerField(u'優先順序', choices=PRIORITY, null=True, db_column='priority') BeginDate = models.DateTimeField(u'開始時間',null=True, db_column='begin_date') EndDate = models.DateTimeField(u'結束時間',null=True, db_column='end_date') SystemDate = models.DateTimeField(u'系統時間', null=False, auto_now_add=True, db_column='system_date') User = models.ForeignKey(User, verbose_name="操作員",null=True, on_delete=models.CASCADE,db_column='user_id') class Meta: db_table = 'task_task' ordering = ['-Priority','TaskId'] verbose_name = verbose_name_plural = "任務" ... class Job(models.Model): STATE_NEW =1 STATE_START=2 STATE_COMPLETED=99 STATE_CANCEL=-1 JOB_STATE=((STATE_NEW,u'新作業'),(STATE_START,u'下達執行'), (STATE_COMPLETED,u'完成'),(STATE_CANCEL,u'已取消'))
凡是有魔法數字的地方都儘量替換成可以閱讀的常量,採用中式英語都是可以推薦的方式:)。
class TaskBiz(object): """description of class""" def task_start(self,obj): success=False if obj.State==Task.STATE_PROCESSED: obj.State=Task.STATE_RUNNING try: obj.save() success = True except Exception: success = False return success ... def task_decompose(self,request,obj): success=True try: job1=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":1,"Source":obj.Source,"Target":'102',"Executor":"AGV01","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job1.save() job2=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":2,"Source":None,"Target":'1',"Executor":"ELEVATOR","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job2.save() job3=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":3,"Source":"102","Target":'104',"Executor":"AGV01","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job3.save() job4=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":4,"Source":'104',"Target":'103',"Executor":"AGV01","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job4.save() job5=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":5,"Source":'1',"Target":'5',"Executor":"ELEVATOR","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job5.save() job6=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":6,"Source":'504',"Target":'503',"Executor":"AGV05","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job6.save() job7=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":7,"Source":'5',"Target":None,"Executor":"ELEVATOR","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job7.save() job8=Job(**{"Task":obj,"TaskNum":obj.TaskNum,"OrderNo":8,"Source":'503',"Target":'05-01-01',"Executor":"AGV05","State":Job.STATE_NEW,\ "Priority":obj.Priority,"Barcode":obj.Barcode,"User":request.user,}) job8.save() #子任務分解完成,並提交資料庫 #更新任務的狀態到“處理完成” obj.State=Task.STATE_PROCESSED obj.save() except Exception: success = False return success
1.6. 小結
本章節我們講述瞭如何通過admin.py來快速的完成頁面功能的構建,並通過自定義action快速的實現了任務分解功能,並根據業務進展也逐步的完善了檢視頁面以內聯表的方式顯示作業詳情。隨著業務功能的增加admin.py的程式碼逐步增多和變得複雜,下一章我們演示如何通過功能內聚和重構程式碼,增加程式碼可讀性和可維護