Русский
Русский
English
Статистика
Реклама

О первоклашках, дистанционке и асинхронном программировании

image

Я дауншифтер. Так получилось, что последние три года мы с женой и младшим ребёнком наслаждаемся сельским пейзажем за окном, свежим воздухом и пением птиц. Удобства в доме, оптический интернет от местного провайдера, мощный бесперебойник и нагрянувший ковид неожиданно сделали идею переезда из мегаполиса не такой уж странной.

Пока я увлеченно занимался веб разработкой, где-то на фоне жена периодически жаловалась на проблемы выбора школы для ребёнка. И тут (вдруг) ребёнок подрос и школьный вопрос встал ребром. Ладно, значит, время пришло. Давайте вместе разберёмся, что же все-таки не так с системой образования в бывшей 1/6 части суши, и что мы с вами можем с этим сделать?

Традиционные методы очного обучения я оставлю за рамками этой статьи. Скажу только, что у обычных школ есть как неоспоримые преимущества, так и серьезные недостатки, к которым, кстати, в последнее время добавилась вынужденная самоизоляция. Здесь мы рассмотрим варианты дистанционного и семейного образования, которые, по целому ряду причин, в последнее время привлекают все больше родителей.

Внесу ясность: дистанционное обучение подразумевает занятия в обычной школе с помощью дистанционных образовательных технологий (ДОТ), а семейное означает добровольный уход из школы и обучение только силами семьи (по сути, это старый добрый экстернат). Впрочем, в любом случае ребёнка нужно прикрепить к какой-либо из доступных школ, как минимум, для сдачи промежуточных аттестаций.

А теперь немного наблюдений из жизни. С вынужденным переводом на дистанционку детей, уже учившихся в обычной школе, все грустно. Школьники воспринимают этот подарок судьбы как своего рода каникулы, родители не привыкли следить за дисциплиной во время занятий и в результате общая успеваемость неизбежно падает.

С первоклашками, особенно в случае семейной формы, у родителей, пожалуй, появляется шанс поставить ребёнка на рельсы, используя естественный интерес и эффект новизны. Лично для меня добиться самостоятельности главная задача. Сидеть и делать с ребёнком домашку я считаю верхом глупости. Конечно, если вы хотите, чтобы ваши дети чего-то добились в жизни и не висели у вас на шее. Я хочу, поэтому моя цель научить ребёнка учиться, правильно задавать вопросы и вообще, думать своей головой.

Ближе к делу. Выбираем государственную школу


Пожалуй, семейное образование мне нравится больше из-за возможности выбрать программу и график обучения. Да и физически посещать школу можно реже. Но выбрать государственную школу, поговорить с директором о прикреплении ребёнка и получить приказ о зачислении в первый класс нужно уже в конце зимы, чтобы в сентябре не было сюрпризов. Хотя, с юридической точки зрения, закон об образовании вроде бы не требует ежегодных аттестаций, дедлайны, по моему опыту, отлично мотивируют, поэтому пусть будут аттестации. Вряд ли любая школа примет нас с распростертыми объятьями, но найти достойный вариант в ближайшем городе мы сможем, я уверен.

Выбираем учебную программу


Именно выбираем. Пытаться составить программу самостоятельно, не имея профильного образования, не разумно. Хотя существуют государственные образовательные ресурсы, такие как Российская Электронная Школа (РЭШ) и Московская Электронная Школа (МЭШ), которых в теории могло было бы хватить, но Оба варианта предоставляют планы уроков, видеозаписи, тесты и учебные пособия. Вот чего мне не удалось найти, так это самих учебников, даже по обязательной программе.

И тут нет самого главного: общения. Обучить ребёнка, показывая ему бесконечные видеоролики и заставляя ставить галочки в тестах, не получится. Значит, нужно либо проводить уроки полностью самостоятельно, либо выбрать одну из онлайн школ.

Выбираем онлайн школу


Мы почти вернулись к тому, с чего начали. Дистанционка? Ладно, присмотримся к ней повнимательней. Как вообще можно организовать учебный процесс удаленно? Тут возникает много вопросов, я подниму только ключевые:

* Живое общение. Что предлагают школы? Скайп, в лучшем случае Тимс. Уроки по Скайпу? Серьёзно? Если я не ошибаюсь, на дворе 2020-й. Открыть перед первоклашкой несколько окон с красивыми разноцветными кнопочками и ждать, что он на них не нажмет, а будет пол-дня послушно слушать скучного дядю или тетю? Ни разу таких детей не видел. А вы?

* Домашка. Точнее, как она попадает к учителю на проверку? На самом деле, это действительно сложный вопрос, возможно, даже не решаемый в принципе. Существующие варианты:

  1. Написать в тетрадке, сфоткать и отправить учителю. Бр-р-р, не хочу заставлять учителей ломать глаза в попытках прочесть мутные фотки с мобильников, сделанные, как правило, по какому-то неписанному закону в темноте.
  2. Отправить скан. Полумера, в общем случае невозможная из-за отсутствия у родителей нужного оборудования.
  3. Оцифровать рукописный ввод с помощью дигитайзера или планшета. Так себе вариант, но об этом чуть позже.
  4. Напечатать текст. В принципе, допустимо, но вот как ребёнок введёт с клавиатуры, например, математическую или химическую формулу? Никак. Плюс, для более продвинутых деток, проблема с плагиатом.
  5. Выполнить онлайн тест. Это, безусловно, самый популярный вариант. Полагаю, большинство школ, включая РЭШ и МЭШ, ориентируются на него. На практике это означает скорее дрессировку, чем обучение. Дети учатся ставить галочки в правильном месте. За бортом остаются предметы, требующие любой формы творчества, например, сочинения, а также диктанты и непопулярное теперь по неведомой мне причине чистописание. Сюда же можно отнести умение отстаивать своё мнение.

* Оценки. Очевидно, выставленные на уроке и при проверке домашних заданий оценки должны попадать в электронный дневник, доступный родителям. И они туда попадают. Вот только не сразу. Я поинтересовался у старших детей, закончивших один из престижных лицеев златоглавой (по иронии судьбы, с информационным уклоном), почему так? Ответ, честно сказать, меня удивил. Оказывается, учителя записывают оценки на бумажку, а после уроков вбивают их в этот самый электронный дневник на государственном портале. И это в то время, как Теслы Илона Маска бороздят просторы космоса

Ладно, пора провести небольшое техническое исследование и проверить, может существуют объективные причины такого положения дел?

Давайте определим требования к гипотетической идеальной платформе для обучения. На самом деле, все просто: дети должны оставаться на уроке, сосредоточившись на том, что говорит и показывает учитель, при необходимости отвечая на вопросы и при желании поднимая руку. По сути, нам нужно окно на полный экран с потоком с учительской камеры, презентацией или интерактивной доской. Самый простой способ добиться этого использовать технологию WebRTC (real-time communications, коммуникации в реальном времени). Эта штука работает в любом более-менее современном браузере, не требует покупки дополнительного оборудования и, к тому же, обеспечивает хорошее качество связи. И да, этот стандарт требует асинхронного программирования как минимум потому, что необходимый JS метод navigator.mediaDevices.getUserMedia() возвращает промис. Вроде все понятно, приступаю к реализации.

Лирическое отступление о выборе фреймворка
В последнее время я все чаше слышу мнение о том, что чистый JavaScript шагнул далеко вперед, проблем с кроссбраузерностью уже нет и фреймворки на фронтэнде не нужны вообще. Особенно часто критике подвергается jQuery. Хорошо, стряхнем пыль со старого доброго JS и сравним:

// Выбрать элементelement = $(selector);element = document.querySelector(selector);// Выбрать элемент внутри элементаelement2 = element.find(selector2);element2 = element.querySelector(selector2);// Скрыть элементelement.hide();  // добавляет стиль display: noneelement.classList.add('hidden');

Тут нужно пояснить, что CSS классу hidden, при желании, можно прописать свойства opacity и transition, что даст эффект fadeIn/fadeOut на чистом CSS. Отлично, давно хотел отказаться от JS анимации!

// Слушать событие onClickelement.click(e => { ... });element.onclick = (e) => { ...  }// Переключить классelement.toggleClass(class_name);element.classList.toggle(class_name);// Создать divdiv = $("<div>");div = document.createElement("div");// Вставить созданный div в element// (это не опечатка, можно писать одинаково)element.append(div);element.append(div);

И т.д и т.п. В целом, код на чистом JS получается немного более многословным, но такова цена повышения производительности и снижения трафика. Нет, я никого не агитирую, но для эксперимента использую нативный JS с удовольствием!

WebRTC предназначен для связи между браузерами напрямую, по технологии точка-точка (p2p). Однако, чтобы установить эту связь, браузеры должны сообщить друг другу о своем намерении общаться. Для этого понадобится сервер сигнализации.

Пример базовой реализации простого видеочата, использующего топологию full mesh
'use strict';(function () {    const selfView = document.querySelector('#self-view'),        remoteMaster = document.querySelector('#remote-master'),        remoteSlaves = document.querySelector('#remote-slaves');    let localStream,        selfStream = null,        socket = null,        selfId = null,        connections = {};    // ***********************    // UserMedia & DOM methods    // ***********************    const init = async () => {        try {            let stream = await navigator.mediaDevices.getUserMedia({                audio: true, video: {                    width: { max: 640 }, height: { max: 480 }                }            });            localStream = stream;            selfStream = new MediaStream();            stream.getVideoTracks().forEach(track => {                selfStream.addTrack(track, stream); // track.kind == 'video'            });            selfView.querySelector('video').srcObject = selfStream;        } catch (e) {            document.querySelector('#self-view').innerHTML =                '<i>Веб камера и микрофон не найдены</i>';            console.error('Local stream not found: ', e);        }        wsInit();    }    const createRemoteView = (id, username) => {        let iDiv = document.querySelector('#pc' + id);        if (!iDiv) {            iDiv = document.createElement('div');            iDiv.className = 'remote-view';            iDiv.id = 'pc' + id;            let iVideo = document.createElement('video');            iVideo.setAttribute('autoplay', 'true');            iVideo.setAttribute('playsinline', 'true');            let iLabel = document.createElement('span');            iDiv.append(iVideo);            iDiv.append(iLabel);            if (!remoteMaster.querySelector('video')) {                remoteMaster.append(iDiv);                iLabel.textContent = 'Ведущий';            } else {                remoteSlaves.append(iDiv);                iLabel.textContent = username;            }            remoteMaster.style.removeProperty('display');        }    }    // *******************************    // Signaling (Web Socket) methods    // *******************************    const wsInit = () => {        socket = new WebSocket(SIGNALING_SERVER_URL);        socket.onopen = function (e) {            log('[socket open] Соединение установлено');        }        socket.onmessage = function (event) {            log('[socket message] Данные получены с сервера', event);            wsHandle(event.data);        }        socket.onclose = function (event) {            if (event.wasClean) {                log('[close] Соединение закрыто чисто, ' +                    `код=${event.code} причина=${event.reason}`);            } else {                log('[socket close] Соединение прервано', event);            }            clearInterval(socket.timer);        }        socket.onerror = function (error) {            logError('[socket error]', error);        }        socket.timer = setInterval(() => {            socket.send('heartbeat');        }, 10000);    }    const wsHandle = async (data) => {        if (!data) {            return;        }        try {            data = JSON.parse(data);        } catch (e) {            return;        }        switch (data.type) {            case 'handshake':                selfId = data.uid;                if (!Object.keys(data.users).length) {                    createRemoteView(selfId, 'Ведущий');                    remoteMaster.querySelector('video').srcObject =                        selfStream;                    selfView.remove();                    break;                } else {                    selfView.style.removeProperty('display');                }                for (let id in data.users) {                    await pcCreate(id, data.users[id]);                }                break;            case 'offer':                await wsHandleOffer(data);                break;            case 'answer':                await wsHandleAnswer(data)                break;            case 'candidate':                await wsHandleICECandidate(data);                break;            default:                break;        }    }    const wsHandleOffer = async (data) => {        let pc = null;        if (!connections[data.src]) {            await pcCreate(data.src, data.username);        }        pc = connections[data.src].pc;        // We need to set the remote description to the received SDP offer        // so that our local WebRTC layer knows how to talk to the caller.        let desc = new RTCSessionDescription(data.sdp);        pc.setRemoteDescription(desc).catch(error => {            logError('handleOffer', error);        });        await pc.setLocalDescription(await pc.createAnswer());        wsSend({            type: 'answer',            target: data.src,            sdp: pc.localDescription        });        connections[data.src].pc = pc; // ???    }    const wsHandleAnswer = async (data) => {        log('*** Call recipient has accepted our call, answer:', data);        let pc = connections[data.src].pc;        // Configure the remote description,        // which is the SDP payload in our 'answer' message.        let desc = new RTCSessionDescription(data.sdp);        await pc.setRemoteDescription(desc).catch((error) => {            logError('handleAnswer', error);        });    }    const wsHandleICECandidate = async (data) => {        let pc = connections[data.src].pc;        let candidate = new RTCIceCandidate(data.candidate);        log('*** Adding received ICE candidate', candidate);        pc.addIceCandidate(candidate).catch(error => {            logError('handleICECandidate', error);        });    }    const wsSend = (data) => {        if (socket.readyState !== WebSocket.OPEN) {            return;        }        socket.send(JSON.stringify(data));    }    // ***********************    // Peer Connection methods    // ***********************    const pcCreate = async (id, username) => {        if (connections[id]) {            return;        }        try {            let pc = new RTCPeerConnection(PC_CONFIG);            pc.onicecandidate = (event) =>                pcOnIceCandidate(event, id);            pc.oniceconnectionstatechange = (event) =>                pcOnIceConnectionStateChange(event, id);            pc.onsignalingstatechange =  (event) =>                pcOnSignalingStateChangeEvent(event, id);            pc.onnegotiationneeded = (event) =>                pcOnNegotiationNeeded(event, id);            pc.ontrack = (event) =>                pcOnTrack(event, id);            connections[id] = {                pc: pc,                username: username            }            if (localStream) {                try {                    localStream.getTracks().forEach(                        (track) => connections[id].pc.addTransceiver(track, {                            streams: [localStream]                        })                    );                } catch (err) {                    logError(err);                }            } else {                // Start negotiation to listen remote stream only                pcOnNegotiationNeeded(null, id);            }            createRemoteView(id, username);        } catch (error) {            logError('Peer: Connection failed', error);        }    }    const pcOnTrack = (event, id) => {        let iVideo = document.querySelector('#pc' + id + ' video');        iVideo.srcObject = event.streams[0];    }    const pcOnIceCandidate = (event, id) => {        let pc = connections[id].pc;        if (event.candidate && pc.remoteDescription) {            log('*** Outgoing ICE candidate: ' + event.candidate);            wsSend({                type: 'candidate',                target: id,                candidate: event.candidate            });        }    }    const pcOnNegotiationNeeded = async (event, id) => {        let pc = connections[id].pc;        try {            const offer = await pc.createOffer();            // If the connection hasn't yet achieved the "stable" state,            // return to the caller. Another negotiationneeded event            // will be fired when the state stabilizes.            if (pc.signalingState != 'stable') {                return;            }            // Establish the offer as the local peer's current            // description.            await pc.setLocalDescription(offer);            // Send the offer to the remote peer.            wsSend({                type: 'offer',                target: id,                sdp: pc.localDescription            });        } catch(err) {            logError('*** The following error occurred while handling' +                ' the negotiationneeded event:', err);        };    }    const pcOnIceConnectionStateChange = (event, id) => {        let pc = connections[id].pc;        switch (pc.iceConnectionState) {            case 'closed':            case 'failed':            case 'disconnected':                pcClose(id);                break;        }    }    const pcOnSignalingStateChangeEvent = (event, id) => {        let pc = connections[id].pc;        log('*** WebRTC signaling state changed to: ' + pc.signalingState);        switch (pc.signalingState) {            case 'closed':                pcClose(id);                break;        }    }    const pcClose = (id) => {        let remoteView = document.querySelector('#pc' + id);        if (connections[id]) {            let pc = connections[id].pc;            pc.close();            delete connections[id];        }        if (remoteView) {            remoteView.remove();        }    }    // *******    // Helpers    // *******    const log = (msg, data) => {        if (!data) {            data = ''        }        console.log(msg, data);    }    const logError = (msg, data) => {        if (!data) {            data = ''        }        console.error(msg, data);    }    init();})();


Сервер сигнализации выполнен на Python фреймвоке aiohttp и представляет собой простую вьюху, тривиально проксирующую запросы WebRTC. Соединение с сервером в этом примере выполнено на веб сокетах. Ну и, в дополнение, через канал сигнализации передаются данные простого текстового чата.

Пример реализации сервера сигнализации
import jsonfrom aiohttp.web import WebSocketResponse, Responsefrom aiohttp import WSMsgTypefrom uuid import uuid1from lib.views import BaseViewclass WebSocket(BaseView):    """ Process WS connections """    async def get(self):        username = self.request['current_user'].firstname or 'Аноним'        room_id = self.request.match_info.get('room_id')        if room_id != 'test_room' and            self.request['current_user'].is_anonymous:            self.raise_error('forbidden')  # @TODO: send 4000        if (self.request.headers.get('connection', '').lower() != 'upgrade' or            self.request.headers.get('upgrade', '').lower() != 'websocket'):            return Response(text=self.request.path)  # ???        self.ws = WebSocketResponse()        await self.ws.prepare(self.request)        self.uid = str(uuid1())        if room_id not in self.request.app['web_sockets']:            self.request.app['web_sockets'][room_id] = {}        self.room = self.request.app['web_sockets'][room_id]        users = {}        for id, data in self.room.items():            users[id] = data['name']        ip = self.request.headers.get(            'X-FORWARDED-FOR',            self.request.headers.get('X-REAL-IP',            self.request.remote))        msg = {            'type': 'handshake',            'uid': str(self.uid),            'users': users, 'ip': ip}        await self.ws.send_str(json.dumps(msg, ensure_ascii=False))        self.room[self.uid] = {'name': username, 'ws': self.ws}        try:            async for msg in self.ws:                if msg.type == WSMsgType.TEXT:                    if msg.data == 'heartbeat':                        print('---heartbeat---')                        continue                    try:                        msg_data = json.loads(msg.data)                        if 'target' not in msg_data or                            msg_data['target'] not in self.room:                            continue                        msg_data['src'] = self.uid                        if 'type' in msg_data and 'target' in msg_data:                            if msg_data['type'] == 'offer':                                msg_data['username'] = username                        else:                            print('INVALID DATA:', msg_data)                    except Exception as e:                        print('INVALID JSON', e, msg)                    try:                        await self.room[msg_data['target']]['ws'].send_json(                            msg_data);                    except Exception as e:                        if 'target' in msg_data:                            self.room.pop(msg_data['target'])        finally:            self.room.pop(self.uid)        return self.ws


Технология WebRTC, кроме видеосвязи, позволяет предоставить браузеру разрешение на захват содержимого дисплея или отдельного приложения, что может оказаться незаменимым при проведении онлайн уроков, вебинаров или презентаций. Отлично, используем.

Я так увлекся современными возможностями видеосвязи, что чуть не забыл о самом главном предмете в классе интерактивной доске. Впрочем, базовая реализация настолько тривиальна, что я не стану загромождать ею эту статью. Просто добавляем canvas, слушаем события перемещения мыши onmousemove (ontouchmove для планшетов) и отправляем полученные координаты всем подключенным точкам через тот же сервер сигнализации.

Тестируем интерактивную доску


Тут понадобится планшет, дигитайзер и живой ребёнок. Заодно проверим возможность оцифровки рукописного ввода.

Для начала я взял старенький планшет Galaxy Tab на андроиде 4.4, самодельный стилус и первые попавшиеся прописи в качестве фона для canvas. Дополнительные программы не устанавливал. Результат меня обескуражил: мой планшет абсолютно не пригоден для письма! То есть водить по нему пальцем без проблем, а вот попасть стилусом в контур буквы, даже такой огромной, как на картинке ниже, уже проблема. Плюс гаджет начинает тупить в процессе рисования, в результате чего линии становятся ломанными. Плюс мне не удалось заставить ребёнка не опирать запястье на экран, отчего под рукой остается дополнительная мазня, а сам планшет начинает тормозить еще больше. Итог: обычный планшет для письма на доске не подходит. Максимум его возможностей двигать пальцем по экрану достаточно крупные фигуры. Но предлагать это школьникам поздновато.

Ладно, у нас ведь чисто теоретическое исследование, верно? Тогда берем дигитайзер (он же графический планшет) Wacom Bamboo формата A8, и наблюдаем за ребёнком.

Замечу, что мой подопытный шести лет от роду получил ноутбук, да еще с графическим пером первый раз в жизни. На получение базовых навыков обращения с пером у нас ушло минут десять, а уже на втором уроке ребёнок пользовался планшетом вполне уверенно, самостоятельно стирал с доски, рисовал рожицы, цветочки, нашу собаку и даже начал тыкать доступные в эпсилон окрестности кнопки, попутно задавая вопросы типа А зачем в школе поднимают руку?. Вот только результат неизменно оставлял желать лучшего. Дело в том, что дизайнеры и художники для прорисовки элемента максимально увеличивают фрагмент изображения, что и делает линии точными. Здесь же мы должны видеть доску целиком, в масштабе 1:1. Тут и взрослый не попадет в линию. Вот что получилось у нас:

image

Итоговый вердикт: ни о каком рукописном вводе не может быть и речи. А если мы хотим поставить руку нашим детям, нужно добиваться этого самостоятельно, на бумаге, школа в этом никак не поможет.

Надо сказать, что ребёнок воспринял все мои эксперименты с восторгом и, более того, с тех пор ходит за мной хвостиком и просит включить прописи. Уже хорошо, полученный навык ему пригодится, только совсем для других целей.

Так или иначе, в результате экспериментов я фактически получил MVP минимально жизнеспособный продукт (minimum viable product), почти пригодный для проведения онлайн уроков, с видео/аудио конференцией, общим экраном, интерактивной доской, простым текстовым чатом и кнопкой поднять руку. Это на случай, если у ребёнка вдруг не окажется микрофона. Да, такое бывает, особенно у детей, не выучивших уроки.

Но в этой бочке меда, к сожалению, есть пара ложек дёгтя.

Тестируем WebRTC


Ложка 1. Поскольку наша видеосвязь использует прямые подключения между клиентами, нужно первым делом проверить масштабируемость такого решения. Для теста я взял старенький ноутбук с двухядерным i5-3230M на борту, и начал подключать к нему клиентов с отключенными веб камерами, то есть эмулируя режим один-ко-многим:

image

Как видите, подопытный ноут в состоянии более-менее комфортно вещать пяти клиентам (при загрузке CPU в пределах 60%). И это при условии снижения разрешения исходящего видеопотока до 720p (640x480px) и frame rate до 15 fps. В принципе, не так уж и плохо, но при подключении класса из нескольких десятков учеников от фулл меша придется отказаться в пользу каскадирования, то есть каждый из первых пяти клиентов проксирует поток следующим пяти и так далее.

Ложка 2. Для создания прямого интерактивного подключения (ICE) между клиентами им нужно обойти сетевые экраны и преобразователи NAT. Для этого WebRTC использует сервер STUN, который сообщает клиентам внешние параметры подключения. Считается, что в большинстве случаев этого достаточно. Но мне почти сразу же повезло:

Как видите, отладчик ругается на невозможность ICE соединения и требует подключения TURN сервера, то есть ретранслятора (relay). А это уже ДОРОГО. Простым сервером сигнализации тут не обойтись. Вывод придётся пропускать все потоки через медиа сервер.

Медиа сервер


Для тестирования я использовал aiortc. Интересная разработка, позволяет подключить браузер напрямую к серверу через WebRTC. Отдельная сигнализация не нужна, можно использовать канал данных самого соединения. Это работает, все мои тестовые точки подключились без проблем. Но вот с производительностью беда. Простое эхо видео/аудио потока с теми же ограничениями 720p и 15fps съели 50% моего виртуального CPU на тестовом VDS. Причём, если увеличить нагрузку до 100%, видео поток не успевает выгружаться клиентам и начинает забивать память, что в итоге приводит к остановке сервера. Очевидно, Питон, который мы любим использовать для задач обработки ввода/вывода, не очень подходит для CPU bound. Придётся поискать более специализированное решение, например, Янус или Джитси.

В любом случае, полноценное решение потребует выделенных серверов, по моим прикидкам, из расчёта 1 ядро на комнату (класс). Это уже стоит денег и выходит за рамки простого тестирования, поэтому в этом месте первую фазу моего исследования я буду считать завершённой.

Выводы


1. Мягко говоря, странно видеть на официальном портале Российской Федерации инструкции по скачиванию и ссылки на регистрацию в программе бывшего потенциального врага 1 (тут про Microsoft Teams). И это в эпоху санкций и импортозамещения.
Нет, лично я за дружбу народов и вообще всяческую толерантность, но неужели только у меня от такой интеграции встают волосы дыбом? Разве нет наших разработок?

2. Интеграция МЭШ/РЭШ со школами. Вообще-то, разработчики МЭШ молодцы, даже с Яндекс.репетитором интеграцию сделали. А как быть с выставлением оценок в реальном времени во время уроков, когда будет API? Или я чего-то не знаю?

3. Выбирая дистанционную или семейную форму обучения, нужно быть честным с собой: переложить ответственность за образование ребёнка на школу не получится. Вся работа по проведению уроков (в случае семейного образования), поддержанию дисциплины и самоорганизованности (в любом случае) полностью ложится на родителей. Нужно отдавать себе в этом отчёт и найти время на занятия. Впрочем, в больших семьях с этим проблем быть не должно.

4. Я не буду приводить здесь ссылки на выбранные нами онлайн школы, чтобы это не сочли рекламой. Скажу только, что мы остановились на частных школах среднего ценового диапазона. В любом случае, окончательный результат будет зависеть от ребёнка и получим мы его не раньше сентября.

Или есть смысл довести до логического конца начатую здесь разработку и организовать свою школу? Что вы думаете? Есть единомышленники, имеющие профильные знания и опыт в области образования?

Полезные ссылки:
Российская Электронная Школа
Московская Электронная Школа
Библиотека МЭШ
Разработчику
Источник: habr.com
К списку статей
Опубликовано: 15.12.2020 10:11:51
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка веб-сайтов

Python

Javascript

Видеоконференцсвязь

Webrtc

Aiohttp

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru