
Я дауншифтер. Так получилось, что последние три года мы с женой и младшим ребёнком наслаждаемся сельским пейзажем за окном, свежим воздухом и пением птиц. Удобства в доме, оптический интернет от местного провайдера, мощный бесперебойник и нагрянувший ковид неожиданно сделали идею переезда из мегаполиса не такой уж странной.
Пока я увлеченно занимался веб разработкой, где-то на фоне жена периодически жаловалась на проблемы выбора школы для ребёнка. И тут (вдруг) ребёнок подрос и школьный вопрос встал ребром. Ладно, значит, время пришло. Давайте вместе разберёмся, что же все-таки не так с системой образования в бывшей 1/6 части суши, и что мы с вами можем с этим сделать?
Традиционные методы очного обучения я оставлю за рамками этой статьи. Скажу только, что у обычных школ есть как неоспоримые преимущества, так и серьезные недостатки, к которым, кстати, в последнее время добавилась вынужденная самоизоляция. Здесь мы рассмотрим варианты дистанционного и семейного образования, которые, по целому ряду причин, в последнее время привлекают все больше родителей.
Внесу ясность: дистанционное обучение подразумевает занятия в обычной школе с помощью «дистанционных образовательных технологий» (ДОТ), а семейное означает добровольный уход из школы и обучение только силами семьи (по сути, это старый добрый экстернат). Впрочем, в любом случае ребёнка нужно прикрепить к какой-либо из доступных школ, как минимум, для сдачи промежуточных аттестаций.
А теперь немного наблюдений из жизни. С вынужденным переводом на дистанционку детей, уже учившихся в обычной школе, все грустно. Школьники воспринимают этот подарок судьбы как своего рода каникулы, родители не привыкли следить за дисциплиной во время занятий и в результате общая успеваемость неизбежно падает.
С первоклашками, особенно в случае семейной формы, у родителей, пожалуй, появляется шанс поставить ребёнка «на рельсы», используя естественный интерес и эффект новизны. Лично для меня добиться самостоятельности — главная задача. Сидеть и делать с ребёнком домашку я считаю
Ближе к делу. Выбираем государственную школу
Пожалуй, семейное образование мне нравится больше из-за возможности выбрать программу и график обучения. Да и физически посещать школу можно реже. Но выбрать государственную школу, поговорить с директором о прикреплении ребёнка и получить приказ о зачислении в первый класс нужно уже в конце зимы, чтобы в сентябре не было сюрпризов. Хотя, с юридической точки зрения, закон об образовании вроде бы не требует ежегодных аттестаций, «дедлайны», по моему опыту, отлично мотивируют, поэтому пусть будут аттестации. Вряд ли любая школа примет нас с распростертыми объятьями, но найти достойный вариант в ближайшем городе мы сможем, я уверен.
Выбираем учебную программу
Именно выбираем. Пытаться составить программу самостоятельно, не имея профильного образования, не разумно. Хотя существуют государственные образовательные ресурсы, такие как Российская Электронная Школа (РЭШ) и Московская Электронная Школа (МЭШ), которых в теории могло было бы хватить, но… Оба варианта предоставляют планы уроков, видеозаписи, тесты и учебные пособия. Вот чего мне не удалось найти, так это самих учебников, даже по обязательной программе.
И тут нет самого главного: общения. Обучить ребёнка, показывая ему бесконечные видеоролики и заставляя ставить галочки в тестах, не получится. Значит, нужно либо проводить уроки полностью самостоятельно, либо выбрать одну из онлайн школ.
Выбираем онлайн школу
Мы почти вернулись к тому, с чего начали. Дистанционка? Ладно, присмотримся к ней повнимательней. Как вообще можно организовать учебный процесс удаленно? Тут возникает много вопросов, я подниму только ключевые:
* Живое общение. Что предлагают школы? Скайп, в лучшем случае Тимс. Уроки по Скайпу? Серьёзно? Если я не ошибаюсь, на дворе 2020-й. Открыть перед первоклашкой несколько окон с красивыми разноцветными кнопочками и ждать, что он на них не нажмет, а будет пол-дня послушно слушать скучного дядю или тетю? Ни разу таких детей не видел. А вы?
* Домашка. Точнее, как она попадает к учителю на проверку? На самом деле, это действительно сложный вопрос, возможно, даже не решаемый в принципе. Существующие варианты:
- Написать в тетрадке, сфоткать и отправить учителю. Бр-р-р, не хочу заставлять учителей ломать глаза в попытках прочесть мутные фотки с мобильников, сделанные, как правило, по какому-то неписанному закону в темноте.
 
- Отправить скан. Полумера, в общем случае невозможная из-за отсутствия у родителей нужного оборудования.
 
- Оцифровать рукописный ввод с помощью дигитайзера или планшета. Так себе вариант, но об этом чуть позже.
 
- Напечатать текст. В принципе, допустимо, но вот как ребёнок введёт с клавиатуры, например, математическую или химическую формулу? Никак. Плюс, для более продвинутых деток, проблема с плагиатом.
 
- Выполнить онлайн тест. Это, безусловно, самый популярный вариант. Полагаю, большинство школ, включая РЭШ и МЭШ, ориентируются на него. На практике это означает скорее дрессировку, чем обучение. Дети учатся ставить галочки в правильном месте. За бортом остаются предметы, требующие любой формы творчества, например, сочинения, а также диктанты и непопулярное теперь по неведомой мне причине чистописание. Сюда же можно отнести умение отстаивать своё мнение.
 
* Оценки. Очевидно, выставленные на уроке и при проверке домашних заданий оценки должны попадать в электронный дневник, доступный родителям. И они туда попадают. Вот только не сразу. Я поинтересовался у старших детей, закончивших один из престижных лицеев златоглавой (по иронии судьбы, с информационным уклоном), почему так? Ответ, честно сказать, меня удивил. Оказывается, учителя записывают оценки на бумажку, а после уроков вбивают их в этот самый электронный дневник на государственном портале. И это в то время, как Теслы Илона Маска бороздят просторы космоса…
Ладно, пора провести небольшое техническое исследование и проверить, может существуют объективные причины такого положения дел?
Давайте определим требования к гипотетической идеальной платформе для обучения. На самом деле, все просто: дети должны оставаться на уроке, сосредоточившись на том, что говорит и показывает учитель, при необходимости отвечая на вопросы и при желании поднимая руку. По сути, нам нужно окно на полный экран с потоком с учительской камеры, презентацией или интерактивной доской. Самый простой способ добиться этого — использовать технологию WebRTC (real-time communications, коммуникации в реальном времени). Эта штука работает в любом более-менее современном браузере, не требует покупки дополнительного оборудования и, к тому же, обеспечивает хорошее качество связи. И да, этот стандарт требует асинхронного программирования как минимум потому, что необходимый JS метод navigator.mediaDevices.getUserMedia() возвращает промис. Вроде все понятно, приступаю к реализации.
// Выбрать элемент
element = $(selector);
element = document.querySelector(selector);
// Выбрать элемент внутри элемента
element2 = element.find(selector2);
element2 = element.querySelector(selector2);
// Скрыть элемент
element.hide();  // добавляет стиль display: none
element.classList.add('hidden');
Тут нужно пояснить, что CSS классу «hidden», при желании, можно прописать свойства opacity и transition, что даст эффект fadeIn/fadeOut на чистом CSS. Отлично, давно хотел отказаться от JS анимации!
// Слушать событие onClick
element.click(e => { ... });
element.onclick = (e) => { ...  }
// Переключить класс
element.toggleClass(class_name);
element.classList.toggle(class_name);
// Создать div
div = $("<div>");
div = document.createElement("div");
// Вставить созданный div в element
// (это не опечатка, можно писать одинаково)
element.append(div);
element.append(div);
И т.д и т.п. В целом, код на чистом JS получается немного более многословным, но такова цена повышения производительности и снижения трафика. Нет, я никого не агитирую, но для эксперимента использую «нативный» JS с удовольствием!
WebRTC предназначен для связи между браузерами напрямую, по технологии точка-точка (p2p). Однако, чтобы установить эту связь, браузеры должны сообщить друг другу о своем намерении общаться. Для этого понадобится сервер сигнализации.
'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 json
from aiohttp.web import WebSocketResponse, Response
from aiohttp import WSMsgType
from uuid import uuid1
from lib.views import BaseView
class 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. Тут и взрослый не попадет в линию. Вот что получилось у нас:

Итоговый вердикт: ни о каком рукописном вводе не может быть и речи. А если мы хотим «поставить руку» нашим детям, нужно добиваться этого самостоятельно, на бумаге, школа в этом никак не поможет.
Надо сказать, что ребёнок воспринял все мои эксперименты с восторгом и, более того, с тех пор ходит за мной хвостиком и просит «включить прописи». Уже хорошо, полученный навык ему пригодится, только совсем для других целей.
Так или иначе, в результате экспериментов я фактически получил MVP — минимально жизнеспособный продукт (minimum viable product), почти пригодный для проведения онлайн уроков, с видео/аудио конференцией, общим экраном, интерактивной доской, простым текстовым чатом и кнопкой «поднять руку». Это на случай, если у ребёнка вдруг не окажется микрофона. Да, такое бывает, особенно у детей, не выучивших уроки.
Но в этой бочке меда, к сожалению, есть пара ложек дёгтя.
Тестируем WebRTC
Ложка №1. Поскольку наша видеосвязь использует прямые подключения между клиентами, нужно первым делом проверить масштабируемость такого решения. Для теста я взял старенький ноутбук с двухядерным i5-3230M на борту, и начал подключать к нему клиентов с отключенными веб камерами, то есть эмулируя режим один-ко-многим:

Как видите, подопытный ноут в состоянии более-менее комфортно вещать пяти клиентам (при загрузке 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. Я не буду приводить здесь ссылки на выбранные нами онлайн школы, чтобы это не сочли рекламой. Скажу только, что мы остановились на частных школах среднего ценового диапазона. В любом случае, окончательный результат будет зависеть от ребёнка и получим мы его не раньше сентября.
Или есть смысл довести до логического конца начатую здесь разработку и организовать свою школу? Что вы думаете? Есть единомышленники, имеющие профильные знания и опыт в области образования?
Полезные ссылки:
Российская Электронная Школа
Московская Электронная Школа
Библиотека МЭШ
Разработчику
 
           
 




Azya
Ну-ну, если добьётесь того, что в начальных классах ребенок будет делать домашнюю работу полностью самостоятельно, надеюсь расскажите потом как.
vladpen Автор
Так уже. Не сказать, что полностью самостоятельно, но ребёнок приучен хотя бы попытаться выполнить задание, и придти либо с результатом на проверку, либо с конкретным вопросом о том, что именно не получается. Разбирать новые темы и выполнять задания каждого нового вида в любом случае приходится вместе.
Иначе получается полная ерунда: ребёнок сидит и ЖДЕТ, пока мама или папа придут делать с ним уроки. И у нас это не первый ребёнок, поверьте.
MichaelProtector
В большинстве случаев с моей практики — не с ним, а ЗА него
Хочу пожелать вам успехов, не у всех родителей хватает терпения на семейное образование.
Azya
Ну это и есть делать с ребенком домашнюю работу, вопрос в необходимой степени помощи, который для разных детей сильно разнится.
vladpen Автор
Я лишь хотел лишний раз подчеркнуть, что лично моя цель в долгосрочной перспективе — не накормить ребёнка, а дать ему удочку и научить ловить рыбу. Я отдаю себе отчет, что это получится не у всех родителей и не со всеми детьми. Но мы честно пытаемся.
Azya
Благая цель, полностью с ней согласен. Просто триггернул на ваше безапелляционное заявление.
DmitryAnatolich
М-м-м, я пошел в первый класс «всего» 20 лет назад, и «в то время» ни у кого и в мыслях не было делать с ребёнками домашние задания.
И не только потому, что все дети были такими умными, а учителя всё отлично объясняли, но и потому, что родители работают и им тоже не помешало бы отдохнуть. Ну и фактор «ой, мы такого не изучали/я этого уже не помню» тоже присутствовал.
Могу через год написать сюда дополнение с личным опытом уже с родительской стороны :).
Azya
Со мной на постоянной основе делали до 4го класса (хотя я этого не помню и по наивности думал, что делал всегда сам), больше 20 лет назад, и по рассказам мамы — делало большинство родителей, а те кому не помогали — хорошо не учились. Но наверно, сейчас это более распространено, родительская опека вообще имеет тенденцию увеличиваться. Тут еще накладывается тот момент, что нагрузка в начальной школе сейчас гораздо выше, многие темы проходят вскользь, а спрашивают полноценно, чтобы ребенок усваивал все без помощи родителей, он должен обладать отличной усидчивостью, умом и мотивацией.
DmitryAnatolich
Опять-таки, смотря где, смотря какой учитель/программа, смотря какая подготовка у ребенка.
Мы с женой предвкушаем, что дочь в следующем году пойдет в школу и будет еще разок учиться читать/писать/считать (как и мы в своё время). Для закрепления, так сказать.
Azya
Ваша правда. Но читать, мне кажется, уже ни в одной школе не учат.
DistortNeo
Ну вот я делал домашнюю работу полностью самостоятельно в младших классах. Вообще, ситуацию, когда родитель вынужден принимать участие в обучении ребёнка, помимо основной работы, считаю ненормальной.
vladpen Автор
Принимать участие надо, а вот делать за ребёнка — однозначно нет.
Я в школе, начиная с первого класса, только приносил родителям дневник с оценками (по вечерам, все работали). Если оценки падали, тогда уже со мной разбирались. Не припоминаю проблем с успеваемостью. Закончил школу с очень приличным средним баллом, что позволило мне поступить в престижный технический вуз. Значит, метод работает. Да и опыт со старшими детьми это подтверждает. Хотя, там были свои трудности, но это уже совсем другая история.