Share via


Автоматизация в доме с помощью Microsoft Robotics Developer Studio 2008

Обычно мы представляем себе роботов как машины из научно-фантастической литературы или промышленные устройства для сборки и покраски автомобилей. В Microsoft Robotics Developer Studio все, что имеет сенсоры и/или исполнительные механизмы, может считаться роботом. В этой статье мы рассмотрим автоматизированный дом как робота и применим к нему модель децентрализованных программных служб (Decentralized Software Services), которая предлагается Microsoft Robotics Studio для реализации некоторых задач по автоматизации дома.

Чарльз Стейси Харрис Третий (Charles Stacy Harris III)

Сложность: средняя

Необходимое время: 6-10 часов

Затраты: $100-$200 на оборудование (необязательно; при использовании эмулятора затраты нулевые)

ПО : Visual Studio или Visual Studio Express, Microsoft Robotics Developer Studio 2008 CTP April, ControlThink Z-Wave PC SDK (EN)

Оборудование: (все является необязательным) панель охраны и автоматизации (Security and Automation Panel) Elk M1G/M1EZ, регулятор яркости света (Dimmer Switches) стандарта Z-Wave, контроллер Z-Wave, например ThinkStick от ControlThink или аналогичный.

Загрузки: Загрузить

Дом-робот

Те из вас, кто слышал обо мне или читал мою предыдущую статью «Автоматизация дома с помощью рабочих процессов Windows (Home Automation with Windows Workflow (EN)), знают, что я фанат домашней автоматизации. Еще большую страсть я испытываю к роботизации и хотел попробовать реализовать некоторые вещи еще с момента появления первой версии Microsoft Robotics Studio. Недавно у меня выдалось несколько свободных дней, и я потратил несколько часов на то, чтобы собрать вместе некоторые примеры решений по автоматизации дома с помощью Microsoft Robotics Developer Studio 2008 CTP April. Подробности о получении CTP-версии см. в центре разработчика Microsoft Robotics Developer Center (EN).

Примеры в этой статье в основном повторяют те, что я написал с помощью рабочего процесса Windows для предыдущей статьи, за исключением того, что здесь применяется принятый в Microsoft Robotics подход, основанный на использовании децентрализованных программных служб (Decentralized Software Services, DSS), и визуальный язык программирования Microsoft Visual Programming Language (VPL). Эти средства упрощают задачи автоматизации. Подробные сведения о DSS, VPL и других технологиях Microsoft Robotics Developer Studio можно найти в центре разработчика Microsoft Robotics Developer Center (EN), здесь же мы рассмотрим лишь некоторые основы. При дальнейшем изучении, кроме кода из этой статьи, вам будут полезны отличные руководства, предлагаемые командой Microsoft Robotics.

Службы, сообщения и порты

Основным компонентом для построения приложений Microsoft Robotics является служба DSS (DSS Service). По сути, службы DSS являются универсальной концепцией, которая может и должна применяться и вне контекста роботизации. DSS-службы имеют состояния, управляемые сообщениями. Эти сообщения поступают в службу через ее порты.

Сообщения, отправляемые службе, являются классами .NET, которые могут иметь содержательную часть, например с информацией о том, изменять ли состояние и каким образом, или сведения о запрашиваемых параметрах состояния. Другие сообщения не оперируют с состоянием напрямую, но могут оказывать на него побочные эффекты. Службы обрабатывают такие сообщения, как CREATE, LOOKUP, UPDATE и др. Кроме того, вы сами можете определить сообщения, являющиеся расширениями базовых сообщений.

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

Напоминаю, что есть отличные интерактивные ресурсы для изучения всех тонкостей работы с Microsoft Robotics, DSS, а также технологий, лежащих в основе Microsoft Robotics Developer Studio. Вероятно, вам потребуется знание основ архитектуры Microsoft Robotics, включая Concurrency and Coordination Runtime (CCR) и знакомство с арбитрами (Arbiters) и итераторами (iterators).

Робо-лось

Система сигнализации и автоматизации в моем доме построена на базе сигнальной панели Elk M1G от Elk Products Incorporated (EN). Эта панель позволяет подключать до 208 входных сигналов с контактных коммутаторов, датчиков движения и т. п. Она также поддерживает до 208 выходов, допускает перепрограммирование и имеет много других возможностей. Я чаще всего использую возможность управления ей через адаптер Ethernet. Поэтому моей первой задачей было написание DSS-службы под названием ElkService для взаимодействия с Elk M1G с использованием сокетов. Моя служба ElkService обменивается сообщениями с Elk M1G по ASCII-протоколу, описанному здесь (EN).

Код для связи службы ElkService с аппаратурой Elk M1G приведен ниже. Заметьте: код выглядит как последовательный, но на самом деле он асинхронный. Метод StreamAdapter.Read запускает задачу выполнения операции асинхронного ввода/вывода, а в строке “yield return (Choice)ioResultPort;” управление возвращается исполняющей среде, и так повторяется для всех задач.

    1: /// <summary>
    2: /// ElkReader открывает соединение с панелью через сокет.
    3: /// Затем программа входит в цикл.
    4: /// </summary>
    5: /// <returns></returns>
    6: public IEnumerator<ITask> ElkReader()
    7: {
    8:     Connect();
    9:     // Отправить запрос панели Elk на получение отчета о состоянии всей зоны.
   10:     SendElkMessage(RequestStrings.ZoneStatus);
   11:  
   12:     byte[] buffer = new byte[256];
   13:  
   14:     int bytesRead = -1;
   15:     Exception ex = null;
   16:  
   17:      do
   18:     {
   19:         var ioResultPort = StreamAdapter.Read(networkStream, buffer, 0, buffer.Length);
   20:  
   21:         yield return (Choice)ioResultPort;
   22:  
   23:         ex = ioResultPort;
   24:         if (ex != null)
   25:             throw ex;
   26:  
   27:         bytesRead = ioResultPort;
   28:         if (bytesRead != 0)
   29:             ProcessRawElkMessage(Encoding.ASCII.GetString(buffer, 0, bytesRead));
   30:  
   31:     } while (bytesRead != 0);
   32: }

Получаемые от оборудования пакеты служба ElkService преобразует из текста в пользовательское сообщение, которое содержит необработанные данные от датчиков, и передает это сообщение в основной порт ElkService. Например, сообщение “ZC”, полученное от оборудования, служба преобразует в сообщение UpdateRawZone, которое содержит необработанные данные от сигнализационной панели. Это делается в методе ProcessRawElkMessage. Вот его код:

    1: public void ProcessRawElkMessage(string message)
    2: {
    3:     string messageType = message.Substring(2, 2);
    4:  
    5:     switch (messageType)
    6:     {
    7:         case "ZC": // Изменение состояния зоны
    8:             var zoneState = new UpdateRawZoneRequest
    9:             {
   10:                 Id = byte.Parse(message.Substring(4, 3)),
   11:                 State = byte.Parse(message.Substring(7, 1),
   12:                               NumberStyles.HexNumber)
   13:             };
   14:  
   15:             var updateZoneMessage = new UpdateRawZone();
   16:             updateZoneMessage.Body = zoneState;
   17:  
   18:             _mainPort.Post(updateZoneMessage);
   19:             break;
   20: …

UpdateRawZoneRequest — это содержательная часть сообщения, используемая для передачи данных о событии изменения зоны подписчикам. UpdateRawZone — тип сообщения, передаваемого в порт ElkService.

    1: [DataContract]
    2: [Description("UpdateRawZone Request Message Payload")]
    3: public class UpdateRawZoneRequest
    4: {
    5:     [DataMember]
    6:     public byte Id { get; set; }
    7:  
    8:     [DataMember]
    9:     public byte State { get; set; }
   10: }
   11:  
   12:  
   13: /// <summary>
   14: /// Обновление состояния зоны Elk
   15: /// </summary>
   16: [Description("UpdateZone request message")]
   17: public class UpdateRawZone : Update<UpdateRawZoneRequest,
   18:         PortSet<DefaultUpdateResponseType, Fault>>
   19: {
   20: }

Код обработки сообщения можно сделать компактней:

 

    1: public void ProcessRawElkMessage(string message)
    2: {
    3:     string messageType = message.Substring(2, 2);
    4:  
    5:     switch (messageType)
    6:     {
    7:         case "ZC": // Состояние зоны изменено
    8:             var updateZoneMessage = new UpdateRawZone
    9:             {
   10:                 Body = new UpdateRawZoneRequest
   11:                 {
   12:                     Id = byte.Parse(message.Substring(4, 3)),
   13:                     State = byte.Parse(message.Substring(7, 1),
   14:                           NumberStyles.HexNumber)
   15:                 }
   16:             };
   17:  
   18:             _mainPort.Post(updateZoneMessage);
   19:             break;
   20: …

UpdateRawZoneHandler — это получатель сообщений на основном порте, который реагирует на сообщения обновления путем изменения внутреннего состояния и уведомления подписчиков об этом изменении.

    1: [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
    2: public IEnumerator<ITask> UpdateRawZoneHandler(UpdateRawZone message)
    3: {
    4:     _state.ZoneStates[message.Body.Id - 1] = message.Body.State;
    5:  
    6:     SendNotification(_submgrPort, message);
    7:  
    8:     message.ResponsePort.Post(DefaultUpdateResponseType.Instance);
    9:  
   10:     yield break;
   11: }
Что происходит в зоне

ElkService — это базовая служба, взаимодействующая с оборудованием щита Elk M1G. Другая служба — ElkZoneSensor — связана с верхним уровнем «матрицы датчиков», подключенных к панели Elk. ElkZoneSensor является как бы «партнером» ElkService. Это означает, что работа ElkZoneSensor зависит от ElkService. Инфраструктура DSS позволяет запускать ElkService (если она еще не стартовала) при запуске службы ElkZoneSensor.

ElkZoneSensor является подписчиком на уведомления об изменениях в зонах от ElkService. Когда такие изменения происходят, ElkService уведомляет об этом ElkZoneSensor, которая обновляет свое состояние и извещает об этом своих подписчиков. ElkZoneSensor использует сообщение верхнего уровня – UpdateZone, – которое включает ID датчика и перечисление, описывающее его состояние.

    1: [DataContract]
    2: public class Zone
    3: {
    4:     [DataMember]
    5:     [Description("The Elk hardware zone id of the sensor")]
    6:     [DataMemberConstructor]
    7:     public byte Id { get; set; }
    8:  
    9:     [DataMember]
   10:     [Description("The Elk defined state of the sensor")]
   11:     public ZoneStatus Status { get; set; }
   12: }
Интенсивность освещения

Следующее, что нам надо в роботизированном доме, — это служба управления освещением. В данном случае я применил универсальный контракт (generic contract) DSS-службы. Универсальный контракт в терминах DSS определяет лишь набор сообщений, на которые отвечает служба, и тип порта, в который эти сообщения будут поступать. Универсальный контракт не содержит собственно кода для реализации действий службы. Одно из его достоинств состоит в том, что вы можете определить контракт, реализовать алгоритмы, использующие этот контракт, а позднее — связать его службы с кодом, который реализует ее функции. Это очень похоже на применение абстрактных интерфейсов, с которыми можно связать множество классов, в действительности реализующих функции этих интерфейсов, в объектно-ориентированном программировании.

Мы начнем с универсальной службы GenericDimmer, отвечающей за регулятор освещенности, и реализуем две конкретных службы: SimulatedDimmer и ZWaveDimmer. SimulatedDimmer лишь записывает в журнал сообщения, чтобы фиксировать изменения состояния, а ZWaveDimmer действительно управляет светом через аппаратный контроллер Z-Wave.

Вот как выглядит контракт службы GenericDimmer:

    1: namespace Robotics.GenericHouseControls.Dimmer
    2: {
    3:  
    4:  
    5:     /// <summary>
    6:     /// Класс контракта GenericDimmer
    7:     /// </summary>
    8:     public sealed class Contract
    9:     {
   10:         /// <summary>
   11:         /// Контракт Dss-службы
   12:         /// </summary>
   13:         [DataMember]
   14:         public const String Identifier = "https://schemas.tempuri.org/2008/04/generichousecontrolsdimmer.html";
   15:     }
   16:  
   17:     /// <summary>
   18:     /// Состояние GenericDimmer
   19:     /// </summary>
   20:     [DataContract]
   21:     public class GenericDimmerState
   22:     {
   23:         [DataMember]
   24:         [DataMemberConstructor(Order = 1)]
   25:         public int Id;
   26:  
   27:         [DataMember]
   28:         public int Level;
   29:     }
   30:  
   31:     /// <summary>
   32:     /// Основной управляющий порт GenericDimmer
   33:     /// </summary>
   34:     [ServicePort(AllowMultipleInstances = true)]
   35:     public class GenericDimmerOperations : PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, HttpGet, On, Off, SetLevel>
   36:     {
   37:     }
   38:  
   39:     /// <summary>
   40:     /// Операция Get GenericDimmer
   41:     /// </summary>
   42:     public class Get : Get<GetRequestType, PortSet<GenericDimmerState, Fault>>
   43:     {
   44:  
   45:         /// <summary>
   46:         /// Операция Get GenericDimmer
   47:         /// </summary>
   48:         public Get()
   49:         {
   50:         }
   51:  
   52:         /// <summary>
   53:         /// Операция Get GenericDimmer
   54:         /// </summary>
   55:         public Get(Microsoft.Dss.ServiceModel.Dssp.GetRequestType body) :
   56:             base(body)
   57:         {
   58:         }
   59:  
   60:         /// <summary>
   61:         /// Операция Get GenericDimmer
   62:         /// </summary>
   63:         public Get(Microsoft.Dss.ServiceModel.Dssp.GetRequestType body, Microsoft.Ccr.Core.PortSet<GenericDimmerState, W3C.Soap.Fault> responsePort) :
   64:             base(body, responsePort)
   65:         {
   66:         }
   67:     }
   68:  
   69:     [DataContract]
   70:     public class OnRequest
   71:     {
   72:     }
   73:  
   74:     [Description("Turn the switch on")]
   75:     public class On : Update<OnRequest, PortSet<DefaultUpdateResponseType, Fault>>
   76:     {
   77:     }
   78:  
   79:     [DataContract]
   80:     public class OffRequest
   81:     {
   82:     }
   83:  
   84:     [Description("Turn the switch off")]
   85:     public class Off : Update<OffRequest, PortSet<DefaultUpdateResponseType, Fault>>
   86:     {
   87:     }
   88:  
   89:     [DataContract]
   90:     public class SetLevelRequest
   91:     {
   92:         [DataMember]
   93:         [DataMemberConstructor]
   94:         public int Level { get; set; }
   95:     }
   96:  
   97:     [Description("Set the dim level")]
   98:     public class SetLevel : Update<SetLevelRequest, PortSet<DefaultUpdateResponseType, Fault>>
   99:     {
  100:     }
  101: }

Этот код определяет только контракт. Для его реализации мы можем запустить утилиту DssNewService с ключом /implement и создать, таким образом, службу на основе этого контракта. Например, команда

dssnewservice /s:SimulatedDimmer /n:Robotics.HouseControls.SimulatedDimmer /i:GenericHouseControls.Y2008.M04.Proxy.dll

позволит создать службу SimulatedDimmer в пространстве имен .NET с именем Robotics.HouseControls.SimulatedDimmer. В этой службе будет заглушка для контракта GenericDimmer, включая код запуска службы, состояние и заглушки обработчиком сообщений.

Вот как выглядит основной файл службы для эмулятора регулятора освещенности с добавленным мной в сгенерированные методы кодом ведения журнала. Для удобочитаемости я удалил некоторые операторы using.

    1: using pxdimmer = Robotics.GenericHouseControls.Dimmer.Proxy;
    2:  
    3: namespace Robotics.HouseControls.SimulatedDimmer
    4: {
    5:  
    6:  
    7:     /// <summary>
    8:     /// Служба управления домом.
    9:     /// </summary>
   10:     [DisplayName("Simulated Dimmer")]
   11:     [Description("The Simulated Dimmer Switch Service")]
   12:     [Contract(Contract.Identifier)]
   13:     [AlternateContract(pxdimmer.Contract.Identifier)]
   14:     public class SimulatedDimmerService : DsspServiceBase
   15:     {
   16:  
   17:         /// <summary>
   18:         /// _state
   19:         /// </summary>
   20:         [ServiceState]
   21:         [InitialStatePartner(Optional = true)]
   22:         private pxdimmer.GenericDimmerState _state = new pxdimmer.GenericDimmerState();
   23:  
   24:         /// <summary>
   25:         /// _main Port
   26:         /// </summary>
   27:         [ServicePort("/simulateddimmer", AllowMultipleInstances = true)]
   28:         private pxdimmer.GenericDimmerOperations _mainPort = new pxdimmer.GenericDimmerOperations();
   29:  
   30:  
   31:         /// <summary>
   32:         /// Конструктор по умолчанию для службы.
   33:         /// </summary>
   34:         public SimulatedDimmerService(DsspServiceCreationPort creationPort) :
   35:             base(creationPort)
   36:         {
   37:         }
   38:  
   39:         /// <summary>
   40:         /// Запуск службы.
   41:         /// </summary>
   42:         protected override void Start()
   43:         {
   44:             base.Start();
   45:  
   46:             // Сюда добавляется код инициализации, специфичный для службы.
   47:             LogInfo(string.Format("Dimmer State => Id:{0} Level:{1}", _state.Id, _state.Level));
   48:         }
   49:  
   50:         [ServiceHandler(ServiceHandlerBehavior.Concurrent)]
   51:         public IEnumerator<ITask> OnHandler(pxdimmer.On update)
   52:         {
   53:             LogInfo(string.Format("Simulated Dimmer => Id:{0} On", _state.Id));
   54:  
   55:             update.ResponsePort.Post(DefaultUpdateResponseType.Instance);
   56:             yield break;
   57:         }
   58:  
   59:         [ServiceHandler(ServiceHandlerBehavior.Concurrent)]
   60:         public IEnumerator<ITask> OffHandler(pxdimmer.Off update)
   61:         {
   62:             LogInfo(string.Format("Simulated Dimmer => Id:{0} Off", _state.Id));
   63:  
   64:             update.ResponsePort.Post(DefaultUpdateResponseType.Instance);
   65:             yield break;
   66:         }
   67:  
   68:         [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
   69:         public IEnumerator<ITask> SetLevelHandler(pxdimmer.SetLevel update)
   70:         {           
   71:             _state.Level = update.Body.Level;
   72:             LogInfo(string.Format("Simulated Dimmer => Id:{0} Level:{1}", _state.Id, update.Body.Level));
   73:  
   74:             update.ResponsePort.Post(DefaultUpdateResponseType.Instance);            
   75:             yield break;
   76:         }
   77:     }
   78: }

Вы можете загрузить код ZWaveDimmer для ознакомления. Он достаточно прост. Если у вас другая технология управления освещенностью, вам будет несложно написать аналогичную программу. Надо лишь написать службу, которая соответствует контракту GenericDimmer, и настроить GenericDimmer с помощью манифеста, связав контракт с реальным кодом, реализующим службу. Подробные сведения о манифесте и работе с редактором манифестов см. в документации по Microsoft Robotics Developer Studio.

Лампочка, зажгись! Лампочка, погасни!

Программой «Hello, World» для всех пакетов домашней автоматизации, которые я испытывал у себя дома, всегда была автоматизация включения/выключения света в кладовой. В моей кладовой есть выключатель Z-Wave внутри, а на двери кладовой установлен контактный выключатель, подключенный к Elk M1G. Когда дверь открыта, мы хотим, чтобы лампочка внутри горела, а когда закрыта — гасла. Теперь, когда у нас есть все необходимое, мы можем представить такую VPL-диаграмму:

clip_image002

Как видите, добавить функцию преобразования текста в речь для озвучивания предупреждений об открывании и закрывании двери в кладовую было нетрудно. Однако когда выяснилось, что находящимся в районе тестовой зоны (а кладовая расположена рядом с кухней) эти предупреждения порядком надоедают, я смог убрать функцию озвучивания также лишь пару раз нажав клавишу «Delete». Это одно из прекрасных качеств модели программирования Microsoft Robotics!

Второй пример демонстрирует работу датчика движения (в данном случае – подключенного к зоне 19 на Elk) для включения/выключения света. Яркость света и длительность включения лампочки вы определяете в своей программе на VPL. В данном примере я установил довольно низкое значение таймера, но вы можете его изменить. Вы можете добавить и свои дополнительные правила, например включать свет только в определенные часы, а еще лучше — включать не основной свет, а только ночник.

clip_image004

На этой диаграмме показано, что при обнаружении движения, когда срабатывает датчик в зоне 19 с кодом состояния ViolatedOpen, свет включается с низким уровнем освещенности и запускается таймер. По завершению интервала таймера свет выключается. Однако если до завершения интервала таймера обнаруживается еще какое-то движение, сообщение SetTimer сбрасывает и перезапускает таймер.

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

Разное

Поскольку у меня не было времени изучить инфраструктуру для тестирования и среду эмуляции Microsoft Robotics, я добавил службу ElkTestDataPlayback, которая позволяет считывать содержимое текстового файла, имитируя поступление реальных данных в ElkService. Для тестирования это вполне сгодилось. Текстовый файл содержит необработанные данные, которые я предварительно записал с панели Elk M1G. Для работы с этими данными в VPL вам надо просто перетащить их на свою диаграмму, установить параметр TestDataFileName, чтобы он указывал на этот файл (с помощью «Set initial configuration»), и отправить службе сообщение, чтобы начать считывание данных из файла.

Внимание! Если у вас оборудование Elk, вам следует раскомментировать строку SpawnIterator(ElkReader) в методе ElkService.Start. Я понимаю, что это временное решение. Пожалуй, было бы лучше написать службу SimulatedElk на основе универсального контракта службы. Когда будет свободное время, я этим займусь. Работы хватит на целые выходные!

Дальнейшие усовершенствования

Поскольку это был мой первый опыт работы с Microsoft Robotics Developer Studio и времени на глубокое изучение возможностей этого инструмента у меня не было, несколько вещей остались нереализованными. Ниже перечислены некоторые функции, которые я хотел бы в будущем расширить.

1. Поддержка других возможностей Elk. Пока я использую только уведомления изменений в зоне (Zone Change). Создание более полной поддержки оборудования Elk не составит особого труда и будет полезно. Для других видов уведомлений имеются заглушки.

2. Универсальные контракты для Elk M1G. Мои службы Elk основаны не на универсальных контрактах, поскольку я недостаточно разбираюсь в разных типах сигнализационных панелей, чтобы провести необходимую систематизацию. Было бы интересно попробовать сделать нечто более универсальное. Я думал над применением массива универсальных контактных датчиков (Generic Contact Sensor), но не уверен, что это подходящая модель. Например, для дверного датчика состояние нарушено (violated) соответствует открытому положению, а для водопроводного датчика — закрытому.

3. Дополнительные возможности Z- Wave. Я не включил генерацию уведомлений об изменении уровня освещенности, поддержку сцен и других функций. При полной поддержке Z-Wave можно еще много всего сделать.

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

5. Поддержка эмуляции. Среда эмуляции — одно из главных преимуществ Microsoft Robotics Developer Studio. Например, с помощью эмулятора датчика движения мы можем проверить описанные выше алгоритмы, сымитировав «нарушение» зоны и инициировав таким образом выполнение соответствующего VPL-кода.

6. Обработка исключений. В дальнейшем я планирую добавить обработку исключений и логику повторных попыток выполнения. В Microsoft Robotics есть очень мощные средства, упрощающие обработку исключений в многозадачной среде, например механизм причинных связей (Causalities).

7. HttpHandlers. Я начал писать обработчик HTTP-запросов в службе ZWaveController. При наличии обработки HTTP-запросов можно создавать клиентские приложения, не являющиеся DSS-службами, которые могут обращаться к DSS-службам.

Еще один интересный эксперимент: интегрировать дом, управляемый средствами Microsoft Robotics, с приложениями, осуществляющими дистанционное управление, которые построены на основе WPF или Silverlight, а может быть, даже использовать управление средствами ПК с Windows Media Center или Windows Home Server.

Надеюсь, вам будет интересно работать с Microsoft Robotics Developer Studio также, как и мне. С нетерпением жду, что расскажут другие пользователи о своем опыте работы с этой фантастической технологией. Если народу будет интересно, я продолжу делиться результатами своих экспериментов с домашней автоматикой. Думаю, что еще долгое время Microsoft Robotics Developer Studio будет основным инструментом в моем арсенале домашней автоматизации.

Замечания о параметрах проекта

Вам придется изменить параметры проектов в решении House.Robot, поскольку Microsoft Robotics Developer Studio у вас будет установлена в других каталогах. Эту работу может упростить утилита командной строки DssProjectMigration.exe. Запустите ее, указав каталог, который она должна просмотреть, и она найдет в этом каталоге и его подкаталогах все входящие в проект файлы и преобразует их в пригодные для компоновки модули с вашими параметрами установки.

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