תוכן עניינים:
- שלב 1: חיישן Conectando O
- שלב 2: Montando a Lixeira
- שלב 3: העלה את Para a Nuvem
- שלב 4: Recuperando Dados Do ThingSpeak
- שלב 5: Criando ו- Aplicação Android
- שלב 6: Recuperando O Feed ללא אנדרואיד
- שלב 7: Mostrando No Mapa
- שלב 8: מסקנה
וִידֵאוֹ: SmartBin: 8 שלבים
2024 מְחַבֵּר: John Day | [email protected]. שונה לאחרונה: 2024-01-31 10:19
Este é um projeto para um system inteligente de coletas, no qual os caminhões de lixo recebem dados das lixeiras, זיהוי ו quantidade de lixo presente em cada uma delas, e uma rota de coleta traçada, com base nas informações recuperadas.
Para montar este projeto, וזה הכרחי:
- NodeMCU
- חיישן Ultrassônico de Distancia
- Caixa de papelão
- פרוטובארד
- קאבוס
- אנדרואיד Dispositivo
שלב 1: חיישן Conectando O
Primeiramente, vamos efetuar a conexão entre o sensor ultrassônico e o NODEMCU. Para tanto, vamos conectar as portas trigger e echo do sensor nas portas D4 e D3 do NodeMCU:
// מגדיר מספרי סיכות #הגדר pino_trigger 2 // D4
#define pino_echo 0 // D3
אם אתה יכול להשתמש בחיישן או במגוון רחב של הדרכות, או לפרופיל של FilipeFlop, disponível aqui.
צף cmMsec, inMsec;
microsec ארוך = ultrasonic.timing ();
cmMsec = ultrasonic.convert (microsec, Ultrasonic:: CM);
inMsec = ultrasonic.convert (microsec, Ultrasonic:: IN);
// מידע Exibe אינו מספק צג סדרתי
Serial.print ("Distancia em cm:");
Serial.print (cmMsec);
Serial.print (" - Distancia em polegadas:");
Serial.println (inMsec);
נתוני מחרוזת = מחרוזת (cmMsec);
Serial.println (נתונים);
שלב 2: Montando a Lixeira
אגורה, vamos montar a lixeira inteligente. Precisaremos conectar o sensor ultrassônico no “teto” da lixeira. לדוגמא, לנצל את כל זה. Em seguida, temos que medir a distância inicial, para saber o valor para a lixeira vazia. אין מאו קאסו, foi de 26, 3 ס מ. Esse é o valor que considerarmos para uma lixeira vazia.
Para simulação, visto que não possuo mais de um sensor ultrassônico, foi feito um algoritmo para salvar randomicamente a distancia lida em 4 lixeiras diferentes.
// Simulando 4 lixeiras
lixeiraID ארוך;
לולאת חלל () {
lixeiraID = אקראי (1, 5);
}
שלב 3: העלה את Para a Nuvem
Agora, precisamos enviar estes dados para a nuvem. Eu escolhi o ThingSpeak, מאת מוכר com o mesmo. Primeiramente, é necessário criar um novo canal, recebendo 4 parâmetros, referentes ao volume de cada lixeira.
ניתן להשתמש באפליקציית ThingSpeak, בהכרח יש צורך בממשק API של תעלות. Siga os passos descritos אין אתר רשמי.
De volta à aplicação, vamos utilizar a biblioteca ESP8266WiFi.h para efetuar conexão com o ThingSpeak, e transferir os dados.
Primeiramente, uma função para efetuar conexão com a rede (defina previamente duas variáveis, ssid e pass , contendo o identifierador e a senha de sua rede).
void connectWifi () {
Serial.print ("מתחבר ל-"+ *ssid);
WiFi.begin (ssid, pass);
בעוד (WiFi.status ()! = WL_CONNECTED) {
עיכוב (500);
Serial.print (".");
}
Serial.println ("");
Serial.print ("Conectado na rede");
Serial.println (ssid);
Serial.print ("IP:");
Serial.println (WiFi.localIP ());
}
Durante o setup, tentamos efetuar a conexão com a rede.
הגדרת בטל () {
Serial.begin (9600);
Serial.println ("Lendo dados do sensor …");
// Conectando ao Wi-Fi
connectWifi ();
}
E, על מנת להדגיש את זה עבור ThingSpeak, אנו יכולים להשתמש ב- HTTP דרך, להעביר את המודעות והמשתמשים שלנו.
void sendDataTS (צף cmMsec, מזהה ארוך) {
if (client.connect (שרת, 80)) {
Serial.println ("Enviando dados para o ThingSpeak");
מחרוזת postStr = apiKey;
postStr += "& field";
postStr += id;
postStr += "=";
postStr += מחרוזת (cmMsec);
postStr += "\ r / n / r / n";
Serial.println (postStr);
client.print ("POST /עדכון HTTP /1.1 / n");
client.print ("מארח: api.thingspeak.com / n");
client.print ("חיבור: סגור / n");
client.print ("X-THINGSPEAKAPIKEY:" + apiKey + "\ n");
client.print ("סוג תוכן: application/x-www-form-urlencoded / n");
client.print ("אורך התוכן:");
client.print (postStr.length ());
client.print ("\ n / n");
client.print (postStr);
עיכוב (1000);
}
client.stop ();
}
O primeiro parâmetro correspondonde à distância em centímetros encontrada pelo sensor ultrassônico. O segundo parâmetro é o ID da lixeira que foi lida (que foi gerado randomicamente, um número de 1 a 4).
O ID da lixeira serve também for identifar para qual campo será feito o upload do valor lido.
שלב 4: Recuperando Dados Do ThingSpeak
O ThingSpeak permite efetuar leitura dos dados do seu canal, através de um serviço retornando um JSON. כפי שניתן להבחין במאפיינים אחרים על מנת להזין את תעלת הסו אסטאו דסקריטס אקווי:
www.mathworks.com/help/thingspeak/get-a-ch…
Neste projeto, optou-se por ler diretamente os dados de cada campo. O padrão de URL para este cenário é:
api.thingspeak.com/channels/CHANNEL_ID/fields/FIELD_NUMBER/last.json?api_key=API_KEY&status=true
Cada campo está descrito no link informado previamente. הדברים החשובים ביותר עבור הפרוייקט סאו:
- CHANNEL_ID: ערוץ número do seu
- FIELD_NUMBER: o número do campo
- API_KEY: תעלת API do seu
כתובת האתר יכולה להוביל לאפליקציות אנדרואיד, לשחזור תוכנות ThingSpeak.
שלב 5: Criando ו- Aplicação Android
אין אנדרואיד סטודיו, crie um novo projeto Android. Para o correto funcionamento da aplicação, ואני צריך להגדיר את ההרשאות כי לא ניתן להשתמש ב- AndroidManifest.
עבור שימוש במפות Google, יש צורך בגרסה זו של גוגל. Siga os passos descritos no link Obter chave de API.
Uma vez com a chave, você deve também configurá-la na aplicação.
מפתח ה- API לממשקי API מבוססי מפות Google מוגדר כמשאב מחרוזת.
(עיין בקובץ "res/values/google_maps_api.xml").
שים לב שמפתח ה- API מקושר למפתח ההצפנה המשמש לחתימת ה- APK. אתה צריך מפתח API אחר לכל מפתח הצפנה, כולל מפתח השחרור המשמש לחתימת ה- APK לפרסום. באפשרותך להגדיר את המפתחות של מטרות באגים ושחרורים ב- src/debug/ו- src/release/.
<meta-data
android: name = "com.google.android.geo. API_KEY"
android: value = "@string /google_maps_key" />
תצורה מלאה של אנדרואיד מניפסט יישומי ao projeto.
נ
שלב 6: Recuperando O Feed ללא אנדרואיד
עיקר מנהלי האנדרואיד, MainActivity, קריא 4 משתנים עבור זיהוי של כל דבר לעשות דבר דבר:
מחרוזת פרטית url_a = "https://api.thingspeak.com/channels/429823/fields/1/last.json?api_key="+API_THINGSPEAK_KEY+"&status=true"; מחרוזת פרטית url_b = "https://api.thingspeak.com/channels/429823/fields/2/last.json?api_key="+API_THINGSPEAK_KEY+"&status=true"; מחרוזת פרטית url_c = "https://api.thingspeak.com/channels/429823/fields/3/last.json?api_key="+API_THINGSPEAK_KEY+"&status=true"; מחרוזת פרטית url_d = "https://api.thingspeak.com/channels/429823/fields/4/last.json?api_key="+API_THINGSPEAK_KEY+"&status=true";
אם אתה יכול להשתמש באתרים אחרים, ניתן להשתמש במכשיר לקלאסה לאנדרואיד, ל- JSONObject. Mais uma vez, vamos criar um objeto para cada URL:
תגובת JSONObjectLixeiraA; תגובת JSONObjectLixeiraB; JSONObject responseLixeiraC; תגובת JSONObjectLixeiraD;
Para abrir a conexão com כתובות אתרים, vamos usar criar uma classe auxiliar, chamada HttpJsonParser. Esta classe será responsável por abrir uma conexão com um URL, efetuar leitura dos dados encontrados, e retornar or objeto JSON montado.
public JSONObject makeHttpRequest (כתובת אתר מחרוזת, שיטת מחרוזת, פרמאות מפות) {
נסה {
בונה Uri. Builder = Uri. Builder חדש (); URL urlObj; String encodedParams = ""; if (params! = null) {for (entry. Map. Entry: params.entrySet ()) {builder.appendQueryParameter (entry.getKey (), entry.getValue ()); }} if (builder.build (). getEncodedQuery ()! = null) {encodedParams = builder.build (). getEncodedQuery ();
}
if ("GET". שווה (שיטה)) {url = url + "?" + encodedParams; urlObj = כתובת אתר חדשה (url); urlConnection = (HttpURLConnection) urlObj.openConnection (); urlConnection.setRequestMethod (שיטה);
} אחר {
urlObj = כתובת אתר חדשה (url); urlConnection = (HttpURLConnection) urlObj.openConnection (); urlConnection.setRequestMethod (שיטה); urlConnection.setRequestProperty ("סוג תוכן", "application/x-www-form-urlencoded"); urlConnection.setRequestProperty ("Content-Length", String.valueOf (encodedParams.getBytes (). length)); urlConnection.getOutputStream (). לכתוב (encodedParams.getBytes ()); } // התחבר לשרת urlConnection.connect (); // קראו שהתגובה היא = urlConnection.getInputStream (); קורא BufferedReader = BufferedReader חדש (InputStreamReader חדש (is)); StringBuilder sb = new StringBuilder (); קו מחרוזת;
// לנתח את התגובה
while ((line = reader.readLine ())! = null) {sb.append (שורה + "\ n"); } קרוב(); json = sb.toString (); // המר את התגובה לאובייקט JSON jObj = JSONObject חדש (json);
} catch (UnsupportedEncodingException e) {
e.printStackTrace (); } catch (ProtocolException e) {e.printStackTrace (); } catch (IOException e) {e.printStackTrace (); } catch (JSONException e) {Log.e ("JSON Parser", "שגיאה בניתוח נתונים" + e.toString ()); } catch (חריגה ה) {Log.e ("חריגה", "שגיאה בניתוח נתונים" + e.toString ()); }
// החזר אובייקט JSON
החזר jObj;
}
}
De volta a atividade principal, vamos efetuar a chamada às urls de forma assíncrona, escrevendo este código dentro do método doInBackground.
מחרוזת מוגנת @Override doInBackground (מחרוזות …) {HttpJsonParser jsonParser = HttpJsonParser חדש ();
responseLixeiraA = jsonParser.makeHttpRequest (url_a, "GET", null);
responseLixeiraB = jsonParser.makeHttpRequest (url_b, "GET", null); responseLixeiraC = jsonParser.makeHttpRequest (url_c, "GET", null); responseLixeiraD = jsonParser.makeHttpRequest (url_d, "GET", null);
להחזיר null;}
Quando o método doInBackgroundé encerrado, o control de execução to Android passas for método onPostExecute. Neste método, vamos criar os objetos Lixeira, e popular com os dados recuperados do ThingSpeak:
חלל מוגן onPostExecute (תוצאה מחרוזת) {pDialog.dismiss (); runOnUiThread (new Runnable () {public void run () {
// ListView listView = (ListView) findViewById (R.id.feedList);
הצג mainView = (הצג) findViewById (R.id.activity_main); if (success == 1) {try {// Cria feedDetail para cada lixeira Lixeira feedDetails1 = Lixeira חדש (); Lixeira feedDetails2 = Lixeira חדש (); Lixeira feedDetails3 = Lixeira חדש (); Lixeira feedDetails4 = Lixeira חדש ();
feedDetails1.setId ('A');
feedDetails1.setPesoLixo (Double.parseDouble (responseLixeiraA.getString (KEY_FIELD1))); feedDetails1.setVolumeLixo (Double.parseDouble (responseLixeiraA.getString (KEY_FIELD1)));
feedDetails2.setId ('B');
feedDetails2.setPesoLixo (Double.parseDouble (responseLixeiraB.getString (KEY_FIELD2))); feedDetails2.setVolumeLixo (Double.parseDouble (responseLixeiraB.getString (KEY_FIELD2)));
feedDetails3.setId ('C');
feedDetails3.setPesoLixo (Double.parseDouble (responseLixeiraC.getString (KEY_FIELD3))); feedDetails3.setVolumeLixo (Double.parseDouble (responseLixeiraC.getString (KEY_FIELD3)));
feedDetails4.setId ('D');
feedDetails4.setPesoLixo (Double.parseDouble (responseLixeiraD.getString (KEY_FIELD4))); feedDetails4.setVolumeLixo (Double.parseDouble (responseLixeiraD.getString (KEY_FIELD4)));
feedList.add (feedDetails1);
feedList.add (feedDetails2); feedList.add (feedDetails3); feedList.add (feedDetails4);
// Calcula dados das lixeiras
מחשבון SmartBinService = SmartBinService חדש (); מחשבון.montaListaLixeiras (feedList);
// רכיבי Recupera
TextView createDate = (TextView) mainView.findViewById (R.id.date); ListView listaDeLixeiras = (ListView) findViewById (R.id.lista); adapter.addAll (feedList);
// נתונים atual
תאריך currentTime = Calendar.getInstance (). GetTime (); SimpleDateFormat simpleDate = חדש SimpleDateFormat ("dd/MM/yyyy"); מחרוזת currentDate = simpleDate.format (currentTime); createDate.setText (KEY_DATE + currentDate + ""); listaDeLixeiras.setAdapter (מתאם);
} catch (JSONException e) {
e.printStackTrace (); }
} אחר {
Toast.makeText (MainActivity.this, "אירעה שגיאה במהלך טעינת הנתונים", Toast. LENGTH_LONG).show ();
}
} }); }
Agora, in tela inicial do aplicativo, serão listados os dados de cada lixeira.
שלב 7: Mostrando No Mapa
מנהלת מנהיגות עו ד, ואנו יכולים להעריך גם את זה, אך לא רשמי.
/ ** מתקשר כאשר המשתמש מקיש על כפתור Mapa*/ public void openMaps (תצוגת תצוגה) {Intent intention = new Intent (this, LixeiraMapsActivity.class);
// Passa a list de lixeiras
צרור צרור = צרור חדש (); bundle.putParcelableArrayList ("lixeiras", feedList); intention.putExtras (חבילה);
startActivity (כוונה);
}
אין מפה, זמנית של התנהגות מבצעת:
- marcar a posição atual do caminha de lixo
- marcar os pontos correspondentes a cada lixeira no mapa
- traçar a rota entre os pontos
בשביל להפעיל אותנו, אנחנו יכולים להשתמש ב- API של Google. Para desenhar as rotas, foram seguidos os passos do do tutorial ציור מסלולי נסיעה בין שני מיקומים באמצעות כיווני Google במפת Google Android Android V2
Primeiro, vamos criar localidades para cada um dos pontos que desejamos marcar:
// מיקומים
זרם LatLng פרטי;
פרטי LatLng lixeiraA; lixeiraB פרטי LatLng; lixeiraC פרטי LatLng; פרטי LatLng lixeiraD;.
לפרטים נוספים על posição atual no mapa, foi criado o método:
private void checkLocationandAddToMap () {// בודק אם המשתמש נתן את ההרשאה אם (ActivityCompat.checkSelfPermission (this, android. Manifest.permission. ACCESS_FINE_LOCATION)! = PackageManager. PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (Android. ACCESS_COARSE_LOCATION)! = PackageManager. PERMISSION_GRANTED) {// בקשת הרשאת מיקום ActivityCompat.requestPermissions (זה, מחרוזת חדשה {android. Manifest.permission. ACCESS_FINE_LOCATION}, LOCATION_REQUEST_CODE); לַחֲזוֹר; }
// אחזור המיקום האחרון הידוע באמצעות ה- Fus
מיקום מיקום = LocationServices. FusedLocationApi.getLastLocation (googleApiClient);
// MarkerOptions משמשים ליצירת סמן חדש. ניתן לציין מיקום, כותרת וכו 'באמצעות MarkerOptions
this.current = LatLng חדש (location.getLatitude (), location.getLongitude ()); MarkerOptions markerOptions = MarkerOptions חדש (). מיקום (הנוכחי).title ("Posição atual");
// הוספת הסמן שנוצר על המפה, העברת המצלמה למיקום
markerOptions.icon (BitmapDescriptorFactory.defaultMarker (BitmapDescriptorFactory. HUE_GREEN)); System.out.println ("+++++++++++++ Passei aqui! ++++++++++++"); mMap.addMarker (markerOptions);
// העבר את המצלמה באופן מיידי למיקום עם זום של 15.
mMap.moveCamera (CameraUpdateFactory.newLatLngZoom (הנוכחי, 15));
// התקרב, אנימציה של המצלמה.
mMap.animateCamera (CameraUpdateFactory.zoomTo (14), 2000, null);
}
Em seguida, para cada lixeira, foram criados métodos similares ao abaixo:
private void addBinALocation () {// בודק אם המשתמש נתן את ההרשאה אם (ActivityCompat.checkSelfPermission (this, android. Manifest.permission. ACCESS_FINE_LOCATION)! = PackageManager. PERMISSION_GRANTED && ActivityCompat.checkSelfPermission (זה, אנדרואיד. ACCESS_COARSE_LOCATION)! = PackageManager. PERMISSION_GRANTED) {// בקשת הרשאת מיקום ActivityCompat.requestPermissions (זה, מחרוזת חדשה {android. Manifest.permission. ACCESS_FINE_LOCATION}, LOCATION_REQUEST_CODE); לַחֲזוֹר; }
// Praça da Estação
קו רוחב כפול = -19.9159578; אורך כפול = -43.9387856; this.lixeiraA = LatLng חדש (קו רוחב, קו אורך);
MarkerOptions markerOptions = MarkerOptions חדש (). מיקום (lixeiraA).title ("Lixeira A");
markerOptions.icon (BitmapDescriptorFactory.defaultMarker (BitmapDescriptorFactory. HUE_RED)); mMap.addMarker (markerOptions); }
כמו posições de latitude e longitude de cada lixeira foram recuperadas através do próprio Maps Google, e deixadas fixas no código. Idealmente, estes valores ficariam salvos em um banco de dados (לדוגמא Firebase). Será a primeira evolução deste projeto!
O último passo agora é traçar as rotas entre os pontos. אם כן, חשוב לי להעריך את זה, ולהשתמש בנושאים הבאים, או בנקודות ציון!
Foi criado um método para traçar a rota entre dois dados pontos:
private String getDirectionsUrl (מקור LatLng, LatLng dest, רשימת נקודות ציון) {
// מקור המסלול
מחרוזת str_origin = "origin ="+origin.latitude+","+origin.longitude;
// יעד המסלול
מחרוזת str_dest = "destination ="+dest.latitude+","+dest.longitude;
// נקודות ציון לאורך המסלול
//waypoints=optimize:true|-19.9227365, -43.9473546 | -19.9168006, -43.9361124 String waypoints = "waypoints = optimize: true"; עבור (נקודת LatLng: waypointsList) {נקודות ציון += "|" + point.latitude + "," + point.longitude; }
// חיישן מופעל
חיישן מחרוזת = "חיישן = שקר";
// בניית הפרמטרים לשירות האינטרנט
פרמטרים של מחרוזות = str_origin+"&"+str_dest+"&"+חיישן+"&"+נקודות ציון;
// פורמט פלט
פלט מחרוזת = "json";
// בניית כתובת ה- URL לשירות האינטרנט
כתובת URL = "https://maps.googleapis.com/maps/api/directions/"+output+"?"+ פרמטרים; System.out.println ("++++++++++++++"+כתובת אתר);
כתובת אתר החזרה;
}
E, por fim, juntando tudo no método principal da classe, onMapReady:
@ביטול חלל ציבורי onMapReady (GoogleMap googleMap) {mMap = googleMap;
checkLocationandAddToMap ();
אם (lixeirasList.get (0).getVolumeLixo ()> Lixeira. MIN_VOLUME_GARBAGE
|| lixeirasList.get (0).getPesoLixo ()-10> Lixeira. MIN_SIZE_GARBAGE) {addBinALocation (); } if (lixeirasList.get (1).getVolumeLixo ()> Lixeira. MIN_VOLUME_GARBAGE || lixeirasList.get (1).getPesoLixo ()> Lixeira. MIN_SIZE_GARBAGE) {addBinBLocation (); } if (lixeirasList.get (2).getVolumeLixo ()> Lixeira. MIN_VOLUME_GARBAGE || lixeirasList.get (2).getPesoLixo ()> Lixeira. MIN_SIZE_GARBAGE) {addBinCLocation (); } if (lixeirasList.get (3).getVolumeLixo ()> Lixeira. MIN_VOLUME_GARBAGE || lixeirasList.get (3).getPesoLixo ()> Lixeira. MIN_SIZE_GARBAGE) {addBinDLocation (); }
// צייר מסלולים
// קבלת כתובת ה- URL ל- Google Directions
נקודות רשימה = ArrayList חדש (); points.add (lixeiraB); points.add (lixeiraC); points.add (lixeiraD);
כתובת אתר מחרוזת = getDirectionsUrl (הנוכחי, lixeiraA, נקודות);
DownloadTask downloadTask = NewTask Download (); // התחל להוריד את נתוני json מ- Google Directions API downloadTask.execute (url); }
Aqui passamos apenas pelos pontos principais. O código completeo do projeto será disponibilizado para consulta.
שלב 8: מסקנה
Este foi um projeto trabalhando conceitos de IoT, mostrando uma das várias opções de conectar dispositivos através da nuvem, e efetuar tomada de decisões sem interferência humana direta. אם אתה יכול לעשות זאת, תוכל להשלים את הפעולות האלה, למשל, או להשתמש בפונטים לאנדרואיד.
מוּמלָץ:
כיצד לבצע אנטנת BiQuade כפולה 4G LTE שלבים פשוטים: 3 שלבים
כיצד להפוך אנטנת 4G LTE BiQuade כפולה לשלבים קלים: לרוב לא עמדתי בפני, אין לי עוצמת אות טובה לעבודות היום-יומיות שלי. לכן. אני מחפש ומנסה סוגים שונים של אנטנות אבל לא עובד. לאחר בזבוז זמן מצאתי אנטנה שאני מקווה לייצר ולבדוק, כי זה עקרון הבנייה לא
עיצוב משחק בקפיצה ב -5 שלבים: 5 שלבים
עיצוב משחק בקפיצה ב -5 שלבים: פליק הוא דרך פשוטה מאוד ליצור משחק, במיוחד משהו כמו פאזל, רומן חזותי או משחק הרפתקאות
זיהוי פנים ב- Raspberry Pi 4B בשלושה שלבים: 3 שלבים
זיהוי פנים ב- Raspberry Pi 4B בשלושה שלבים: במדריך זה אנו הולכים לבצע זיהוי פנים ב- Raspberry Pi 4 עם Shunya O/S באמצעות ספריית Shunyaface. Shunyaface היא ספריית זיהוי/זיהוי פנים. הפרויקט שואף להשיג את מהירות הזיהוי והזיהוי המהירה ביותר עם
מהדורת ליל כל הקדושים של Arduino - מסך קופץ זומבים (שלבים עם תמונות): 6 שלבים
מהדורת ליל כל הקדושים של Arduino - מסך פופ -אאוט של זומבים (צעדים עם תמונות): רוצה להפחיד את החברים שלך ולעשות רעש צורח בהלווין? או סתם רוצה לעשות מתיחה טובה? המסך הקופץ הזה של זומבים יכול לעשות זאת! במדריך זה אלמד אותך כיצד ליצור זומבים קופצים בקלות באמצעות Arduino. ה- HC-SR0
SmartBin: 4 שלבים
SmartBin: המטרה העיקרית של פרויקט זה היא ליצור מכשיר אלקטרוני שמשתמש לפחות ב- Raspberry Pi אחד. הצוות מורכב מ -5 מהנדסי מכונות עתידיים ומהנדס אוטומציה אחד. הפרויקט שלנו כולל יצירת פח אשפה שנפתח ונסגר